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) {
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
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++
|
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
|
||||||
|
|
|
@ -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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue