Add a tentative stack walker using eh_elfs
This is only a beginning: this does so far load the appropriate eh_elf.so files and read a context. Unwinding is still absent.
This commit is contained in:
parent
be221f68d5
commit
f587eadbcc
3 changed files with 206 additions and 0 deletions
18
stack_walker/Makefile
Normal file
18
stack_walker/Makefile
Normal file
|
@ -0,0 +1,18 @@
|
|||
CXX=g++
|
||||
CXXLIBS=-ldl
|
||||
CXXFLAGS=-O0 -g -fPIC -Wall -Wextra
|
||||
|
||||
TARGET=libstack_walker.so
|
||||
OBJS=stack_walker.o
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(CXXFLAGS) -shared $< $(CXXLIBS) -o $@
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET)
|
151
stack_walker/stack_walker.cpp
Normal file
151
stack_walker/stack_walker.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#include "stack_walker.hpp"
|
||||
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#include <link.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
|
||||
typedef void* dl_handle_t;
|
||||
|
||||
struct MemoryMapEntry {
|
||||
MemoryMapEntry():
|
||||
beg(0), end(0), offset(0), obj_path(), eh_dl_handle(nullptr)
|
||||
{}
|
||||
|
||||
uintptr_t beg, end;
|
||||
int offset;
|
||||
std::string obj_path;
|
||||
|
||||
dl_handle_t eh_dl_handle;
|
||||
};
|
||||
|
||||
typedef std::map<uintptr_t, MemoryMapEntry> MemoryMap;
|
||||
|
||||
static MemoryMap memory_map;
|
||||
|
||||
|
||||
std::string readlink_rec(const char* path) {
|
||||
char buf[2][1024];
|
||||
int parity = 1;
|
||||
strcpy(buf[1], path);
|
||||
|
||||
do {
|
||||
int rc = readlink(buf[parity], buf[1-parity], 1024);
|
||||
parity = 1 - parity;
|
||||
if(rc < 0) {
|
||||
if(errno == EINVAL)
|
||||
break;
|
||||
}
|
||||
} while(true);
|
||||
|
||||
return std::string(buf[1 - parity]);
|
||||
}
|
||||
|
||||
int fill_memory_map_callback(
|
||||
struct dl_phdr_info* info,
|
||||
size_t /*size*/,
|
||||
void* /*data*/)
|
||||
{
|
||||
for(int sec = 0; sec < info->dlpi_phnum; ++sec) {
|
||||
const ElfW(Phdr)& cur_hdr = info->dlpi_phdr[sec];
|
||||
if(cur_hdr.p_type != PT_LOAD || (cur_hdr.p_flags & PF_X) == 0)
|
||||
continue;
|
||||
if(std::string(info->dlpi_name).find("linux-vdso")
|
||||
!= std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MemoryMapEntry entry;
|
||||
entry.beg = info->dlpi_addr + cur_hdr.p_vaddr;
|
||||
entry.obj_path = std::string(info->dlpi_name);
|
||||
entry.offset = cur_hdr.p_offset;
|
||||
|
||||
entry.end = entry.beg + cur_hdr.p_memsz;
|
||||
|
||||
if(entry.obj_path.empty()) { // The source binary itself
|
||||
entry.obj_path = readlink_rec("/proc/self/exe");
|
||||
}
|
||||
|
||||
memory_map.insert(std::make_pair(entry.beg, entry));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stack_walker_close() {
|
||||
for(auto& mmap_entry_pair: memory_map) {
|
||||
auto& mmap_entry = mmap_entry_pair.second;
|
||||
|
||||
if(mmap_entry.eh_dl_handle != nullptr)
|
||||
dlclose(mmap_entry.eh_dl_handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool stack_walker_init() {
|
||||
if(dl_iterate_phdr(&fill_memory_map_callback, nullptr) != 0) {
|
||||
stack_walker_close();
|
||||
return false;
|
||||
}
|
||||
|
||||
for(const auto& mmap_entry: memory_map) {
|
||||
printf("%012lx-%012lx %08x %s\n",
|
||||
mmap_entry.second.beg,
|
||||
mmap_entry.second.end,
|
||||
mmap_entry.second.offset,
|
||||
mmap_entry.second.obj_path.c_str());
|
||||
}
|
||||
|
||||
for(auto& mmap_entry_pair: memory_map) {
|
||||
auto& mmap_entry = mmap_entry_pair.second;
|
||||
|
||||
// Find SO's basename
|
||||
size_t last_slash = mmap_entry.obj_path.rfind("/");
|
||||
if(last_slash == std::string::npos)
|
||||
last_slash = 0;
|
||||
else
|
||||
last_slash++;
|
||||
std::string basename(mmap_entry.obj_path, last_slash);
|
||||
|
||||
// Load the SO
|
||||
std::string eh_elf_name = basename + ".eh_elf.so";
|
||||
mmap_entry.eh_dl_handle = dlopen(eh_elf_name.c_str(), RTLD_LAZY);
|
||||
|
||||
if(mmap_entry.eh_dl_handle == nullptr) {
|
||||
fprintf(stderr,
|
||||
"Error: cannot load shared object %s.\ndlerror: %s\n",
|
||||
eh_elf_name.c_str(),
|
||||
dlerror());
|
||||
stack_walker_close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unwind_context_t get_context() {
|
||||
unwind_context_t out;
|
||||
ucontext_t uctx;
|
||||
|
||||
if(getcontext(&uctx) < 0) {
|
||||
assert(0); // Cleaner code will come later — TODO
|
||||
}
|
||||
|
||||
out.rip = uctx.uc_mcontext.gregs[REG_RIP];
|
||||
out.rsp = uctx.uc_mcontext.gregs[REG_RSP];
|
||||
out.rbp = uctx.uc_mcontext.gregs[REG_RBP];
|
||||
|
||||
unwind_context(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool unwind_context(unwind_context_t& ctx) {
|
||||
}
|
||||
|
||||
void walk_stack(const std::function<void(const unwind_context_t&)>& mapped) {
|
||||
}
|
37
stack_walker/stack_walker.hpp
Normal file
37
stack_walker/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);
|
Loading…
Reference in a new issue