Initial version

This commit is contained in:
mollismerx 2016-09-16 17:45:35 +02:00
commit a4d59d09f7
45 changed files with 2641 additions and 0 deletions

51
Arg.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef ELFSPY_ARG_H
#define ELFSPY_ARG_H
#include "elfspy/Variadic.h"
#include "elfspy/Thunk.h"
#include "elfspy/ThunkHandle.h"
#include "elfspy/Capture.h"
namespace spy
{
/**
* @namespace spy
* @class Arg
* This captures one argument of an invocation
*/
template <size_t N, typename... ArgTypes>
class Arg : public Thunk<ArgTypes...>,
public Capture<typename Variadic<N, ArgTypes...>::Type>
{
public:
virtual void invoke(ArgTypes&&... args) override;
};
template <size_t N, typename... ArgTypes>
void Arg<N, ArgTypes...>::invoke(ArgTypes&&... args)
{
using Var = Variadic<N, ArgTypes...>;
// forward arguments to avoid copying
this->captures_.push_back(Var::get(std::forward<ArgTypes>(args)...));
}
template <size_t N, typename H>
inline auto arg(H& hook)
-> ThunkHandle<typename H::template ExportN<Arg>::template Type<N>>
{
return hook.thunks();
}
template <size_t N, typename H>
inline auto new_arg(H& hook)
-> ThunkHandle<typename H::template ExportN<Arg>::template Type<N>>*
{
using TP = ThunkHandle<typename H::template ExportN<Arg>::template Type<N>>;
return new TP(hook.thunks());
}
} // namespace elfspy
#endif

11
Call.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "Call.h"
namespace spy
{
void Call::invoke()
{
++count_;
}
} // namespace spy

46
Call.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef ELFSPY_CALL_H
#define ELFSPY_CALL_H
#include "elfspy/Thunk.h"
#include "elfspy/ThunkHandle.h"
namespace spy
{
/**
* @namespace spy
* @class Call
* Will count each call made to the Thunk's function
*/
class Call : public Thunk<>
{
public:
virtual void invoke() override;
std::size_t count() const;
private:
std::size_t count_ = 0;
};
inline std::size_t Call::count() const
{
return count_;
}
template <typename H>
inline ThunkHandle<Call> call(H&& hook)
{
return hook.no_arg_thunks();
}
template <typename H>
inline ThunkHandle<Call>* new_call(H&& hook)
{
return new ThunkHandle<Call>(hook.no_arg_thunks());
}
} // namespace spy
#endif

59
Capture.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef ELFSPY_CAPTURE_H
#define ELFSPY_CAPTURE_H
#include <vector>
namespace spy
{
/*
* @namespace spy
* @class Capture
* Base class for holding copies of 1 or nore values encountered during
* invocations. Provides methods for comparison in asserts after invocation.
*/
template <typename T>
class Capture
{
public:
using Container = std::vector<T>;
using const_iterator = typename Container::const_iterator;
const_iterator begin() const;
const_iterator end() const;
std::size_t size() const;
const T& value(std::size_t index = 0) const;
protected:
Container captures_;
};
template <typename T>
inline typename Capture<T>::const_iterator Capture<T>::begin() const
{
return captures_.begin();
}
template <typename T>
inline typename Capture<T>::const_iterator Capture<T>::end() const
{
return captures_.end();
}
template <typename T>
//inline typename Capture<T>::size_type Capture<T>::size() const
inline std::size_t Capture<T>::size() const
{
return captures_.size();
}
template <typename T>
const T& Capture<T>::value(std::size_t index) const
{
return captures_[index];
}
} // namespace spy
#endif

311
ELFInfo.cpp Normal file
View File

@ -0,0 +1,311 @@
#include "elfspy/ELFInfo.h"
#include <sys/mman.h>
#include <elf.h>
#include <unistd.h>
#include <string.h>
#include "elfspy/Report.h"
#include "elfspy/Fail.h"
#ifdef __x86_64__
using Elf_Phdr = Elf64_Phdr;
using Elf_Ehdr = Elf64_Ehdr;
using Elf_Shdr = Elf64_Shdr;
using Elf_Rel = Elf64_Rel;
using Elf_Rela = Elf64_Rela;
using Elf_Sym = Elf64_Sym;
#define ELF_STTYPE(X) ELF64_ST_TYPE(X)
#define ELF_STBIND(X) ELF64_ST_BIND(X)
#define ELF_R_SYM(X) ELF64_R_SYM(X)
#define ELF_R_TYPE(X) ELF64_R_TYPE(X)
#else
using Elf_Phdr = Elf32_Phdr;
using Elf_Ehdr = Elf32_Ehdr;
using Elf_Shdr = Elf32_Shdr;
using Elf_Rel = Elf32_Rel;
using Elf_Rela = Elf32_Rela;
using Elf_Sym = Elf32_Sym;
#define ELF_STTYPE(X) ELF32_ST_TYPE(X)
#define ELF_STBIND(X) ELF32_ST_BIND(X)
#define ELF_R_SYM(X) ELF32_R_SYM(X)
#define ELF_R_TYPE(X) ELF32_R_TYPE(X)
#endif
namespace
{
long page_size = sysconf(_SC_PAGESIZE);
union Address
{
unsigned char* pointer_;
size_t value_;
void round_down();
void round_up();
};
inline void Address::round_down()
{
value_ /= page_size;
value_ *= page_size;
}
inline void Address::round_up()
{
value_ += page_size - 1;
value_ /= page_size;
value_ *= page_size;
}
} // namespace
namespace spy
{
ELFInfo::ELFInfo(unsigned char* data, const char* name)
{
data_ = data;
name_ = name;
if (!is_elf()) {
Fail() << "Not ELF data - no ELF header found in " << name;
}
}
bool ELFInfo::is_elf() const
{
auto elf = reinterpret_cast<Elf_Ehdr*>(data_);
return elf->e_ident[EI_MAG0] == ELFMAG0
&& elf->e_ident[EI_MAG1] == ELFMAG1
&& elf->e_ident[EI_MAG2] == ELFMAG2
&& elf->e_ident[EI_MAG3] == ELFMAG3;
}
void* ELFInfo::find_section(const char* name)
{
auto elf = reinterpret_cast<Elf_Ehdr*>(data_);
auto header = data_ + elf->e_shoff;
auto section_names =
reinterpret_cast<Elf_Shdr*>(header + elf->e_shstrndx * elf->e_shentsize);
auto names = reinterpret_cast<char*>(elf) + section_names->sh_offset;
// .shstrtab is the "section header" string table, it is indexed in elf header
for (size_t n = 0; n != elf->e_shnum; ++n, header += elf->e_shentsize) {
auto section = reinterpret_cast<Elf_Shdr*>(header);
if (strcmp(names + section->sh_name, name) == 0) {
return section;
}
}
if (strncmp(name_, "/lib", 4) != 0 && strncmp(name_, "/usr", 4) != 0) {
Report() << "no " << name << " section found in " << name_;
}
return nullptr;
}
void ELFInfo::unprotect(unsigned char* base, const char* name)
{
auto section = reinterpret_cast<Elf_Shdr*>(find_section(name));
if (section) {
Address begin;
Address end;
begin.pointer_ = base + section->sh_addr;
end.pointer_ = begin.pointer_ + section->sh_size;
begin.round_down();
end.round_up();
size_t size = end.value_ - begin.value_;
if (mprotect(begin.pointer_, size,
PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
spy::Fail() << "Cannot set " << name << " to writable for " << name_;
}
}
}
void ELFInfo::unprotect(unsigned char* base)
{
// by default this memory is read only - ELFspy needs to change it
unprotect(base, ".got");
unprotect(base, ".got.plt");
}
ELFInfo::Symbol ELFInfo::get_symbol_rela(size_t value)
{
Symbol result;
auto str_tab = reinterpret_cast<Elf_Shdr*>(find_section(".dynstr"));
if (!str_tab) {
return result;
}
auto strings = reinterpret_cast<const char*>(data_ + str_tab->sh_offset);
// find dynamic symbol table
auto symbol_table = reinterpret_cast<Elf_Shdr*>(find_section(".dynsym"));
if (!symbol_table) {
return result;
}
// find symbol by value in dynamic symbol table
size_t index = 0;
auto symbols = data_ + symbol_table->sh_offset;
auto end = symbols + symbol_table->sh_size;
for ( ; symbols < end; symbols += symbol_table->sh_entsize) {
auto symbol = reinterpret_cast<Elf_Sym*>(symbols);
if (ELF_STTYPE(symbol->st_info) == STT_FUNC && symbol->st_value == value) {
// the symbol is defined in this file as 0 is undefined.
// an STT_GNU_IFUNC will not match here as the function value will be the
// the resulting function of the resolver function and therefore at a
// different address
result.name_ = strings + symbol->st_name;
break;
}
++index;
}
if (!result.name_) {
return result; // not found
}
auto rela_plt = reinterpret_cast<Elf_Shdr*>(find_section(".rela.plt"));
if (!rela_plt) {
return result;
}
size_t rela_plt_entries = rela_plt->sh_size / rela_plt->sh_entsize;
unsigned char* rela = data_ + rela_plt->sh_offset;
// attempt to find symbol in .rela.plt
for (size_t n = 0; n != rela_plt_entries; ++n) {
auto reloc = reinterpret_cast<Elf_Rela*>(rela);
if (ELF_R_TYPE(reloc->r_info) == R_X86_64_JUMP_SLOT) {
// find symbol by index
size_t symbol_index = ELF_R_SYM(reloc->r_info);
if (symbol_index == index) {
result.rela_offset_ = reloc->r_offset;
break;
}
}
rela += rela_plt->sh_entsize;
}
// a symbol will only be present in .rela.plt iff it was used in the ELF
// object - otherwise only .dynsym and .symtab will contain it
// this means, no GOT entry for function and in this case result.rela_plt
// returns 0
return result;
}
// look for STT_IFUNC symbols by finding the function address in the relocated
// .rela.plt entries. STT_IFUNC are rare, but time(time_t*) is one of them
ELFInfo::Symbol ELFInfo::get_indirect_symbol_rela(unsigned char* base,
void* function)
{
Symbol result;
auto str_tab = reinterpret_cast<Elf_Shdr*>(find_section(".dynstr"));
if (!str_tab) {
return result;
}
auto strings = reinterpret_cast<const char*>(data_ + str_tab->sh_offset);
// find dynamic symbol table
auto symbol_table = reinterpret_cast<Elf_Shdr*>(find_section(".dynsym"));
if (!symbol_table) {
return result;
}
auto symbols = data_ + symbol_table->sh_offset;
// find function in relocated GOT
auto rela_plt = reinterpret_cast<Elf_Shdr*>(find_section(".rela.plt"));
if (!rela_plt) {
return result;
}
size_t rela_plt_entries = rela_plt->sh_size / rela_plt->sh_entsize;
unsigned char* rela = data_ + rela_plt->sh_offset;
for (size_t n = 0; n != rela_plt_entries; ++n) {
auto reloc = reinterpret_cast<Elf_Rela*>(rela);
if (ELF_R_TYPE(reloc->r_info) == R_X86_64_IRELATIVE
&& *reinterpret_cast<void**>(base + reloc->r_offset) == function) {
result.rela_offset_ = reloc->r_offset;
// find symbol by r_addend in dynamic symbol table
auto end = symbols + symbol_table->sh_size;
for ( ; symbols < end; symbols += symbol_table->sh_entsize) {
auto symbol = reinterpret_cast<Elf_Sym*>(symbols);
if (ELF_STTYPE(symbol->st_info) == STT_GNU_IFUNC
&& symbol->st_value == reloc->r_addend) {
result.name_ = strings + symbol->st_name;
break;
}
}
break;
}
rela += rela_plt->sh_entsize;
}
return result; // whether found or not
}
size_t ELFInfo::get_symbol_rela_dyn(const char* name)
{
auto str_tab = reinterpret_cast<Elf_Shdr*>(find_section(".dynstr"));
if (!str_tab) {
return 0;
}
auto strings = reinterpret_cast<const char*>(data_ + str_tab->sh_offset);
// find symbol table
auto symbol_table = reinterpret_cast<Elf_Shdr*>(find_section(".dynsym"));
if (symbol_table == nullptr) {
return 0;
}
auto symbols = data_ + symbol_table->sh_offset;
size_t symbol_entries = symbol_table->sh_size / symbol_table->sh_entsize;
// find symbol in .rela.dyn
for (const char* section : { ".rela.dyn", ".rela.plt" }) {
auto rela_dyn = reinterpret_cast<Elf_Shdr*>(find_section(section));
if (!rela_dyn) {
return 0;
}
size_t rela_entries = rela_dyn->sh_size / rela_dyn->sh_entsize;
unsigned char* rela = data_ + rela_dyn->sh_offset;
for (size_t n = 0; n != rela_entries; ++n) {
auto reloc = reinterpret_cast<Elf_Rel*>(rela);
size_t symbol_index = ELF_R_SYM(reloc->r_info);
if (symbol_index < symbol_entries) {
symbol_index *= symbol_table->sh_entsize;
auto symbol = reinterpret_cast<Elf_Sym*>(symbols + symbol_index);
const char* symbol_name = strings + symbol->st_name;
if (strcmp(name, symbol_name) == 0) {
return reloc->r_offset;
}
}
rela += rela_dyn->sh_entsize;
}
}
return 0;
}
std::unordered_map<std::string, unsigned char*>
ELFInfo::get_vtables(unsigned char* base)
{
if (!is_elf()) {
return { };
}
auto elf = reinterpret_cast<Elf_Ehdr*>(data_);
auto header = data_ + elf->e_shoff;
// get string table by finding the right strtab section
// .dynstr is for DYNSYM section (it has a non-zero address)
// .shstrtab is the "section header" string table, it is indexed in elf header
// .strtab is for SYMTAB, which is what we want here
auto str_tab = reinterpret_cast<Elf_Shdr*>(find_section(".strtab"));
if (!str_tab) {
return { };
}
auto strings = reinterpret_cast<const char*>(data_ + str_tab->sh_offset);
// find symbol table
auto symbol_table = reinterpret_cast<Elf_Shdr*>(find_section(".symtab"));
if (symbol_table == nullptr) {
return { };
}
std::unordered_map<std::string, unsigned char*> vtables;
// look for virtual tables
auto symbols = data_ + symbol_table->sh_offset;
auto end = symbols + symbol_table->sh_size;
for ( ; symbols < end; symbols += symbol_table->sh_entsize) {
auto symbol = reinterpret_cast<Elf_Sym*>(symbols);
// check if is defined (value != 0)
if (symbol->st_value && ELF_STTYPE(symbol->st_info) == STT_OBJECT) {
// check if it is a virtual table (starts with "_ZTV")
auto name = strings + symbol->st_name;
if (strncmp(name, "_ZTV", 4) == 0) {
vtables[name + 4] = base + symbol->st_value;
}
}
}
return vtables;
}
} // namespace spy

73
ELFInfo.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef ELFSPY_ELFINFO_H
#define ELFSPY_ELFINFO_H
#include <string>
#include <unordered_map>
#include "elfspy/ELFObject.h"
namespace spy
{
/**
* @namespace spy
* @class ELFInfo
* can read data in ELF format and extract information from it
*/
class ELFInfo : public ELFObject
{
public:
/**
* @param data ELF data
* @param name name of ELF data for diagnostics
*/
ELFInfo(unsigned char* data, const char* name);
/// @return true if data is in ELF format
bool is_elf() const;
struct Symbol
{
const char* name_ = nullptr;
size_t rela_offset_ = 0;
};
/**
* find symbol and offset name in .rela.plt if symbol is defined in file
* @param value symbol offset value expected to be in symtab
* @return name and offset (empty if not defined in file)
*/
Symbol get_symbol_rela(size_t value);
/**
* find symbol and offset name in .rela.plt if symbol is defined as STT_IFUNC
* @param base base address of ELF object in memory
* @param function function pointer
* @return name and offset (0 if not defined in file)
*/
Symbol get_indirect_symbol_rela(unsigned char* base, void* function);
/**
* find symbol offset in .rela.dyn if symbol is found as undefined in file
* @param name symbol name
* @return offset (0 if not defined in file)
*/
size_t get_symbol_rela_dyn(const char* name);
/**
* remove write protection from the areas in memory that need to change
* @param base base address in memory
*/
void unprotect(unsigned char* base);
/**
* find vtable adresses from symbol table
* @param base - offset to return addresses relative to
* @return map of type name to vtable address
*/
std::unordered_map<std::string, unsigned char*>
get_vtables(unsigned char* base = nullptr);
private:
unsigned char* data_;
void* find_section(const char* name);
void unprotect(unsigned char* base, const char* name);
};
} // namespace spy
#endif

19
ELFObject.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef ELFSPY_ELFOBJECT_HXX
#define ELFSPY_ELFOBJECT_HXX
namespace spy
{
struct ELFObject
{
const char* name_ = "no name";
unsigned char* base_ = nullptr;
size_t size_ = 0;
size_t symbols_ = 0;
size_t strings_ = 0;
size_t rela_plt_ = 0;
};
} // namespace spy
#endif

28
Error.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "elfspy/Error.h"
#include <iostream>
namespace
{
std::function<void(const char*)> reporter =
[](const char* text)
{
std::cerr << text << std::endl;
};
} // namespace
namespace spy
{
void error(const char* text)
{
reporter(text);
}
void set_reporter(const std::function<void(const char*)>& reporter)
{
::reporter = reporter;
}
} // namespace spy

14
Error.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef ELFSPY_ERROR_H
#define ELFSPY_ERROR_H
#include <functional>
namespace spy
{
void error(const char* text);
void set_reporter(const std::function<void(const char*)>& reporter);
} // namespace spy
#endif

14
Fail.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "elfspy/Fail.h"
#include <cstdlib>
namespace spy
{
Fail::~Fail()
{
show();
std::exit(-1);
}
} // namespace spy

17
Fail.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef ELFSPY_FAIL_H
#define ELFSPY_FAIL_H
#include "elfspy/Report.h"
namespace spy
{
class Fail : public Report
{
public:
~Fail();
};
} // namespace spy
#endif

60
Fake.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef ELFSPY_FAKE_H
#define ELFSPY_FAKE_H
#include "elfspy/Lambda.h"
namespace spy
{
/**
* @namespace spy
* @class Fake
* This replaces an existing function with another function, so that all calls
* to the program are made to the other function.
* The constructor installs the new function in place of the function, the
* destructor uninstalls it
*/
template <typename H, typename ReturnType, typename... ArgTypes>
class Fake
{
public:
Fake(H& hook, ReturnType (*func)(ArgTypes...));
~Fake();
private:
H& hook_;
ReturnType (*func_)(ArgTypes...);
};
template <typename H, typename ReturnType, typename... ArgTypes>
inline Fake<H, ReturnType, ArgTypes...>::
Fake(H& hook, ReturnType (*func)(ArgTypes...))
:hook_(hook)
{
func_ = hook_.patch(func);
}
template <typename H, typename ReturnType, typename... ArgTypes>
inline Fake<H, ReturnType, ArgTypes...>::~Fake()
{
hook_.patch(func_);
}
template <typename H, typename ReturnType, typename... ArgTypes>
inline auto fake(H& hook, ReturnType (*patch)(ArgTypes...))
-> Fake<H, ReturnType, ArgTypes...>
{
return { hook, patch };
}
template <typename H, typename ReturnType, typename... ArgTypes>
inline auto new_fake(H& hook, ReturnType (*patch)(ArgTypes...))
-> Fake<H, ReturnType, ArgTypes...>*
{
return new Fake<H, ReturnType, ArgTypes...>(hook, patch);
}
} // namespace elfspy
#endif

22
Function.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef ELFSPY_FUNCTION_H
#define ELFSPY_FUNCTION_H
namespace spy
{
/**
* @namespace spy
* @union Function
* Conversion helper from function to address
*/
template <typename R, typename... Args>
union Function
{
R (*address_)(Args...);
void* pointer_;
};
} // namespace spy
#endif

243
GOTEntry.cpp Normal file
View File

@ -0,0 +1,243 @@
#include "GOTEntry.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <link.h>
#include <elf.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <unordered_map>
#include "elfspy/MFile.h"
#include "elfspy/ELFInfo.h"
#include "elfspy/Fail.h"
#include "elfspy/Report.h"
#ifdef __x86_64__
#define Elf_Phdr Elf64_Phdr
#define Elf_Ehdr Elf64_Ehdr
#else
#define Elf_Phdr Elf32_Phdr
#define Elf_Ehdr Elf32_Ehdr
#endif
namespace
{
int argc;
char** argv;
std::string program_name;
bool debug = getenv("ELFSPY_DEBUG") != nullptr;
// TODO: just copy the char it's in memory
std::unordered_map<std::string, unsigned char*> vtables;
std::vector<spy::ELFObject> elf_objects;
void resolve_program_name()
{
struct stat file_info;
std::string file_name = argv[0];
const char* path = getenv("PATH");
if (!path || argv[0][0] == '/') {
path = "";
}
char* path_copy = strdup(path);
char* path_token = path_copy;
char* save;
for ( ; ; ) {
int rv = stat(file_name.c_str(), &file_info);
if (rv == 0) {
program_name = file_name;
break;
}
const char* dir = strtok_r(path_token, ":", &save);
if (dir == nullptr) {
break;
}
path_token = nullptr; // for second call to strtok_r
file_name = dir;
file_name.push_back('/');
file_name.append(argv[0]);
}
free(path_copy);
}
int read_shared_object(struct dl_phdr_info* info, size_t size, void* data)
{
// the ELF header for each loaded object
unsigned char* elf_root;
const char* name;
if (!info->dlpi_addr) {
// this is the executable itself - sadly the name was not included in info
elf_root = nullptr;
name = program_name.c_str();
} else {
elf_root = reinterpret_cast<unsigned char*>(info->dlpi_addr);
name = info->dlpi_name;
}
if (name && *name) {
spy::MFile file(name);
spy::ELFInfo elf(file.address(), name);
elf.base_ = elf_root;
elf.size_ = file.size();
elf.unprotect(elf_root);
elf_objects.push_back(elf);
auto entries = elf.get_vtables(elf_root);
vtables.insert(entries.begin(), entries.end());
}
return 0;
}
} // namespace
namespace spy
{
// arguments left in for future compatibility
void GOTEntry::initialise(int argc, char** argv)
{
::argc = argc;
::argv = argv;
resolve_program_name();
if (program_name.empty()) {
Fail() << "Cannot determine absolute file name of executable";
}
dl_iterate_phdr(&read_shared_object, nullptr);
}
// base + rela.plt if symbol is found and defined
// base + rela.dyn if symbol is found and undefined
void* GOTEntry::set(void* function)
{
if (entries_.empty()) {
auto address = reinterpret_cast<unsigned char*>(function);
// first find ELF object where it is defined
ELFInfo::Symbol symbol;
std::string symbol_name;
ELFObject* defined = nullptr;
for (auto& object : elf_objects) {
// check if it is even possible for the function to exist in the object
size_t offset = address - object.base_;
if (offset < object.size_) {
// that the offset is inside the size is a tell-tale the symbol can be
// found
MFile elf_file(object.name_);
ELFInfo elf(elf_file.address(), object.name_);
// find name in elf file where it is defined
symbol = elf.get_symbol_rela(offset);
if (symbol.rela_offset_) {
// symbol was defined and used, offset is from the .rela.plt section
make_entry(object.base_ + symbol.rela_offset_);
}
if (symbol.name_) {
// symbol was defined
symbol_name = symbol.name_;
defined = &object;
break;
}
}
}
if (!defined) {
// rare case of looking for an STT_IFUNC when not found at all
for (auto& object : elf_objects) {
MFile elf_file(object.name_);
ELFInfo elf(elf_file.address(), object.name_);
symbol = elf.get_indirect_symbol_rela(object.base_, function);
if (symbol.rela_offset_) {
// symbol was defined, offset is from the .rela.plt section
make_entry(object.base_ + symbol.rela_offset_);
symbol_name = symbol.name_;
defined = &object;
break;
}
}
}
if (!defined) {
Report() << "cannot find definition of function " << function;
return nullptr;
}
// find entries where the symbol is used in other ELF functions
for (auto& object : elf_objects) {
if (&object == defined) {
continue;
}
MFile elf_file(object.name_);
ELFInfo elf(elf_file.address(), object.name_);
size_t rela_offset = elf.get_symbol_rela_dyn(symbol_name.c_str());
if (rela_offset) {
// symbol was undefined, offset is from the .rela.dyn section
make_entry(object.base_ + rela_offset);
}
}
}
// in file where it is defined find the .rela.plt
// if undefined find the name in the .rela.dyn using .symtab index for name
// and with strcmp
if (original_ == nullptr) {
original_ = function;
}
current_ = function;
return original_;
}
void GOTEntry::make_entry(void** address)
{
// store the previous value of the entry which points to @plt+6 as it has been
// resolved, @plt there is a indirect jump to the contents of the entry, so
// if it is set to @plt instead @plt+6 an infinite loop will occur
Entry entry;
entry.address_ = address;
entry.restore_ = *entry.address_;
entries_.push_back(entry);
}
void** GOTEntry::get_vtable_entry(const std::type_info& type,
const MethodPointer& method)
{
if (method.is_virtual()) {
// this is a virtual function
auto seek = vtables.find(type.name());
if (seek != vtables.end()) {
unsigned char* vtable = seek->second;
// there seems to be two entries that are not accounted for in the index
auto location = vtable + sizeof(void*) * 2 + (method.index_ & ~1UL);
return reinterpret_cast<void**>(location);
}
Report() << "Could not find virtual function";
return nullptr;
}
// non virtual function
return nullptr;
}
void* GOTEntry::spy_with(void* function)
{
if (!spy_count_) {
patch(function);
}
++spy_count_;
return original_;
}
void* GOTEntry::patch(void* function)
{
for (auto entry : entries_) {
*entry.address_ = function;
}
void* previous = current_;
current_ = function;
return previous;
}
void GOTEntry::restore()
{
--spy_count_;
if (!spy_count_) {
for (auto entry : entries_) {
*entry.address_ = entry.restore_;
}
}
current_ = original_;
}
} // namespace spy

94
GOTEntry.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef ELFSPY_GOTENTRY_H
#define ELFSPY_GOTENTRY_H
#include <typeinfo>
#include <vector>
#include "elfspy/MethodPointer.h"
namespace spy
{
/**
* @namespace spy
* @class GOTEntry
* Handle a function of interest and all its references in all Global Offset
* Tables
*/
class GOTEntry
{
public:
GOTEntry() = default;
/**
* @param function address of function
* @return original unaltered address of function
*/
void* set(void* function);
/**
* replace function with other function and reference count
* @param function to enter in Global Offset Tables instead of original
* @return original entry in Global Offset Table instead of original
*/
void* spy_with(void* function);
/**
* count down reference count from spy_with and restore original entry in
* Global Offset Tables if count is zero
*/
void restore();
/**
* replace function with other function
* @param function to enter in Global Offset Tables instead of original
* @return original entry in Global Offset Table instead of original
*/
void* patch(void* function);
/**
* get the address of a virtual function
* @param type type info of class
* @param method method function pointer
* @return address of address of virtual function for that class
*/
static void** get_vtable_entry(const std::type_info& type,
const MethodPointer& method);
static void initialise(int argc, char** argv);
const void* current() const;
const void* original() const;
/**
* add an entry to the function pointer addresses
* @param address address of address of function
*/
void make_entry(void** address);
private:
struct Entry
{
void** address_;
void* restore_;
};
std::vector<Entry> entries_; // all entries
size_t spy_count_ = 0;
void* current_ = nullptr;
void* original_ = nullptr;
void make_entry(unsigned char* address);
};
inline const void* GOTEntry::current() const
{
return original_;
}
inline const void* GOTEntry::original() const
{
return original_;
}
inline void GOTEntry::make_entry(unsigned char* address)
{
make_entry(reinterpret_cast<void**>(address));
}
} // namespace spy
#endif

115
Hook.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef ELFSPY_HOOK_H
#define ELFSPY_HOOK_H
#include "elfspy/HookImpl.h"
#include "elfspy/GOTEntry.h"
#include "elfspy/Function.h"
#include "elfspy/MethodInfo.h"
#include "elfspy/Thunk.h"
namespace spy
{
/**
* @namespace spy
* @class Hook
* An instance is a function that is executed after a call to a
* function/method and before it is run, essentially injected between the two.
* It is very lightweight as it only holds reference(s) to static data members.
* Calling convention is unified between methods and functions so that methods
* are functions where the first type in Args... is the class type.
*/
template <typename CRTP, typename ReturnType, typename... ArgTypes>
class Hook : public HookImpl<CRTP, ReturnType, ArgTypes...>
{
public:
using Result = ReturnType;
/**
* typedefs to re-export variadic ArgTypes to other variadic templates
*/
template <template <typename...> class Other, typename... Extra>
struct Export
{
using Type = Other<Extra..., ArgTypes...>;
};
template <template <size_t, typename...> class Other>
struct ExportN
{
template <size_t N>
using Type = Other<N, ArgTypes...>;
};
/**
* construct from function pointer
*/
Hook(ReturnType (*function)(ArgTypes...));
/**
* construct from method pointer
*/
Hook(MethodInfo<ReturnType, ArgTypes...> method);
/// restore original function
~Hook();
/**
* invoke the original function
* @param args arguments to pass
* @return return value of function
*/
static ReturnType invoke_real(ArgTypes... args);
operator const GOTEntry&() const;
private:
static GOTEntry got_entry_;
using Base = HookImpl<CRTP, ReturnType, ArgTypes...>;
};
template <typename CRTP, typename ReturnType, typename... ArgTypes>
GOTEntry Hook<CRTP, ReturnType, ArgTypes...>::got_entry_;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
Hook<CRTP, ReturnType, ArgTypes...>::Hook(ReturnType (*function)(ArgTypes...))
{
// use union to get and convert address of function
Function<ReturnType, ArgTypes...> converter;
converter.address_ = function;
got_entry_.set(converter.pointer_);
converter.address_ = &Base::thunk;
converter.pointer_ = got_entry_.spy_with(converter.pointer_);
Base::patch_ = Base::real_ = converter.address_;
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline Hook<CRTP, ReturnType, ArgTypes...>::
Hook(MethodInfo<ReturnType, ArgTypes...> method)
{
got_entry_.set(method.address_);
if (method.vtable_entry_) {
got_entry_.make_entry(method.vtable_entry_);
}
Function<ReturnType, ArgTypes...> converter;
converter.address_ = &Base::thunk;
converter.pointer_ = got_entry_.spy_with(converter.pointer_);
Base::patch_ = Base::real_ = converter.address_;
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline Hook<CRTP, ReturnType, ArgTypes...>::~Hook()
{
got_entry_.restore();
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline ReturnType Hook<CRTP, ReturnType, ArgTypes...>::invoke_real(ArgTypes... args)
{
return (*Base::real_)(args...);
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline Hook<CRTP, ReturnType, ArgTypes...>::operator const GOTEntry&() const
{
return got_entry_;
}
} // namespace spy
#endif

95
HookBase.h Normal file
View File

@ -0,0 +1,95 @@
#ifndef ELFSPY_HOOKBASE_H
#define ELFSPY_HOOKBASE_H
#include <vector>
#include "elfspy/Thunk.h"
namespace spy
{
/**
* @namespace spy
* @class HookBase
* Contains the common part of HookImpl for void and non-void return values
*/
template <typename CRTP, typename ReturnType, typename... ArgTypes>
class HookBase
{
public:
/// @return collection to register thunks that process function arguments
static std::vector<Thunk<ArgTypes...>*>& thunks();
/// @return collection to register thunks not using function arguments
static std::vector<Thunk<>*>& no_arg_thunks();
typedef ReturnType (*Signature)(ArgTypes...);
/**
* replace the original function with a different one
* @param function to replace original with
* @return previous function being called (typically the original)
*/
static Signature patch(Signature function);
protected:
static Signature real_;
static Signature patch_;
static std::vector<Thunk<ArgTypes...>*> thunks_;
static std::vector<Thunk<>*> no_arg_thunks_;
static void run_thunks(ArgTypes&&... args);
};
template <typename CRTP, typename ReturnType, typename... ArgTypes>
typename HookBase<CRTP, ReturnType, ArgTypes...>::Signature
HookBase<CRTP, ReturnType, ArgTypes...>::real_ = nullptr;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
typename HookBase<CRTP, ReturnType, ArgTypes...>::Signature
HookBase<CRTP, ReturnType, ArgTypes...>::patch_ = nullptr;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
std::vector<Thunk<ArgTypes...>*>
HookBase<CRTP, ReturnType, ArgTypes...>::thunks_;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
std::vector<Thunk<>*> HookBase<CRTP, ReturnType, ArgTypes...>::no_arg_thunks_;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline std::vector<Thunk<ArgTypes...>*>&
HookBase<CRTP, ReturnType, ArgTypes...>::thunks()
{
return thunks_;
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline
std::vector<Thunk<>*>& HookBase<CRTP, ReturnType, ArgTypes...>::no_arg_thunks()
{
return no_arg_thunks_;
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
void HookBase<CRTP, ReturnType, ArgTypes...>::run_thunks(ArgTypes&&... args)
{
for (auto thunk : thunks_)
{
thunk->invoke(std::forward<ArgTypes>(args)...);
}
for (auto stub : no_arg_thunks_)
{
stub->invoke();
}
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
typename HookBase<CRTP, ReturnType, ArgTypes...>::Signature
HookBase<CRTP, ReturnType, ArgTypes...>::
patch(typename HookBase<CRTP, ReturnType, ArgTypes...>::Signature function)
{
auto copy = patch_;
patch_ = function;
return copy;
}
} // namespace spy
#endif

82
HookImpl.h Normal file
View File

@ -0,0 +1,82 @@
#ifndef ELFSPY_HOOKIMPL_H
#define ELFSPY_HOOKIMPL_H
#include "elfspy/HookBase.h"
namespace spy
{
/**
* @namespace spy
* @class HookImpl
* Part of the Hook implementation to distinguish between void and non-void
* return values.
*/
template <typename CRTP, typename ReturnType, typename... ArgTypes>
class HookImpl : public HookBase<CRTP, ReturnType, ArgTypes...>
{
public:
/// @return collection to register thunks that process return value
static std::vector<Thunk<ReturnType&&>*>& return_thunks();
protected:
static std::vector<Thunk<ReturnType&&>*> return_thunks_;
static ReturnType&& use_return(ReturnType&& result);
static ReturnType thunk(ArgTypes... args);
using Base = HookBase<CRTP, ReturnType, ArgTypes...>;
};
template <typename CRTP, typename ReturnType, typename... ArgTypes>
std::vector<Thunk<ReturnType&&>*>
HookImpl<CRTP, ReturnType, ArgTypes...>::return_thunks_;
template <typename CRTP, typename ReturnType, typename... ArgTypes>
inline std::vector<Thunk<ReturnType&&>*>&
HookImpl<CRTP, ReturnType, ArgTypes...>::return_thunks()
{
return return_thunks_;
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
ReturnType&&
HookImpl<CRTP, ReturnType, ArgTypes...>::use_return(ReturnType&& result)
{
for (auto trap : return_thunks_)
{
trap->invoke(std::forward<ReturnType>(result));
}
return std::forward<ReturnType>(result);
}
template <typename CRTP, typename ReturnType, typename... ArgTypes>
ReturnType HookImpl<CRTP, ReturnType, ArgTypes...>::thunk(ArgTypes... args)
{
Base::run_thunks(std::forward<ArgTypes>(args)...);
return use_return(
std::forward<ReturnType>((*Base::patch_)(std::forward<ArgTypes>(args)...)));
}
/// specialisation for void return
template <typename CRTP, typename... ArgTypes>
class HookImpl<CRTP, void, ArgTypes...>
: public HookBase<CRTP, void, ArgTypes...>
{
protected:
static void thunk(ArgTypes... args);
private:
using Base = HookBase<CRTP, void, ArgTypes...>;
};
template <typename CRTP, typename... ArgTypes>
void HookImpl<CRTP, void, ArgTypes...>::thunk(ArgTypes... args)
{
Base::run_thunks(std::forward<ArgTypes>(args)...);
(*Base::patch_)(std::forward<ArgTypes>(args)...);
}
} // namespace spy
#endif

75
Lambda.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef ELFSPY_LAMBDA_H
#define ELFSPY_LAMBDA_H
namespace spy
{
/**
* @namespace spy
* @class Lambda
* This replaces an existing function with a lambda, so that all calls to
* the program are made to the lambda.
* The constructor installs the lambda in place of the function, the destructor
* uninstalls it
*/
template <typename H, typename L, typename ReturnType, typename... ArgTypes>
class Lambda
{
public:
Lambda(H& hook, L& lambda);
~Lambda();
private:
static ReturnType function(ArgTypes... argtypes);
H& hook_;
L lambda_;
static Lambda* instance_;
ReturnType (*func_)(ArgTypes...);
};
template <typename H, typename L, typename ReturnType, typename... ArgTypes>
Lambda<H, L, ReturnType, ArgTypes...>*
Lambda<H, L, ReturnType, ArgTypes...>::instance_ = nullptr;
template <typename H, typename L, typename ReturnType, typename... ArgTypes>
inline Lambda<H, L, ReturnType, ArgTypes...>::Lambda(H& hook, L& lambda)
:hook_(hook)
,lambda_(lambda)
{
instance_ = this;
func_ = hook.patch(&Lambda<H, L, ReturnType, ArgTypes...>::function);
}
template <typename H, typename L, typename ReturnType, typename... ArgTypes>
inline Lambda<H, L, ReturnType, ArgTypes...>::~Lambda()
{
hook_.patch(func_);
instance_ = nullptr;
}
template <typename H, typename L, typename ReturnType, typename... ArgTypes>
ReturnType Lambda<H, L, ReturnType, ArgTypes...>::function(ArgTypes... argtypes)
{
return instance_->lambda_(std::forward<ArgTypes>(argtypes)...);
}
template <typename H, typename L>
inline auto fake(H& hook, L& lambda)
-> typename H::template Export<Lambda, H, L, typename H::Result>::Type
{
return { hook, lambda };
}
template <typename H, typename L>
inline auto new_fake(H& hook, L& lambda)
-> typename H::template Export<Lambda, H, L, typename H::Result>::Type*
{
using Install =
typename H::template Export<Lambda, H, L, typename H::Result>::Type;
return new Install(hook, lambda);
}
} // namespace elfspy
#endif

40
MFile.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "elfspy/MFile.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include "elfspy/Fail.h"
namespace spy
{
MFile::MFile(const char* file_name)
{
fd_ = open(file_name, O_RDONLY, 0);
if (fd_ < 0) {
Fail() << "Cannot open " << file_name;
}
struct stat file;
if (fstat(fd_, &file) < 0) {
Fail() << "Cannot fstat " << file_name;
}
size_ = file.st_size;
address_ = reinterpret_cast<unsigned char*>(
mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_, 0));
if (address_ == MAP_FAILED) {
Fail() << "Cannot mmap " << file_name;
}
}
MFile::~MFile()
{
munmap(address_, size_);
close(fd_);
}
} // namespace spy

52
MFile.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef ELFSPY_MFILE_H
#define ELFSPY_MFILE_H
#include <stddef.h>
namespace spy
{
class Symbol;
/**
* @namespace spy
* @class MFile
* maps a file to memory
*/
class MFile
{
public:
MFile(const char* file_name);
MFile(const MFile&) = delete;
MFile& operator=(const MFile&) = delete;
~MFile();
const char* name() const;
unsigned char* address() const;
size_t size() const;
private:
const char* name_;
int fd_;
size_t size_;
unsigned char* address_;
};
inline const char* MFile::name() const
{
return name_;
}
inline unsigned char* MFile::address() const
{
return address_;
}
inline size_t MFile::size() const
{
return size_;
}
} // namespace spy
#endif

75
Method.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef ELFSPY_METHOD_H
#define ELFSPY_METHOD_H
#include <typeinfo>
#include "elfspy/GOTEntry.h"
#include "elfspy/MethodPointer.h"
#include "elfspy/MethodInfo.h"
namespace spy
{
/**
* @namespace spy
* @union Method
* Conversion union for all different method signatures
*/
template <typename R, typename T, typename... Args>
union Method
{
R (T::*method_)(Args...);
R (T::*const_method_)(Args...) const;
R (T::*volatile_method_)(Args...) volatile;
R (T::*cv_method_)(Args...) const volatile;
Method(R (T::*method)(Args...));
Method(R (T::*const_method)(Args...) const);
Method(R (T::*volatile_method)(Args...) volatile);
Method(R (T::*cv_method)(Args...) const volatile);
R (*function_)(T*, Args...);
void* address_;
MethodPointer impl_;
MethodInfo<R, T*, Args...> resolve();
};
template <typename R, typename T, typename... Args>
inline Method<R, T, Args...>::Method(R (T::*method)(Args...))
{
method_ = method;
}
template <typename R, typename T, typename... Args>
inline Method<R, T, Args...>::Method(R (T::*const_method)(Args...) const)
{
const_method_ = const_method;
}
template <typename R, typename T, typename... Args>
inline Method<R, T, Args...>::Method(R (T::*volatile_method)(Args...) volatile)
{
volatile_method_ = volatile_method;
}
template <typename R, typename T, typename... Args>
inline Method<R, T, Args...>::Method(R (T::*cv_method)(Args...) const volatile)
{
cv_method_ = cv_method;
}
template <typename R, typename T, typename... Args>
MethodInfo<R, T*, Args...> Method<R, T, Args...>::resolve()
{
MethodInfo<R, T*, Args...> info;
if (impl_.is_virtual()) {
info.vtable_entry_ = GOTEntry::get_vtable_entry(typeid(T), impl_);
info.address_ = *info.vtable_entry_;
} else {
info.vtable_entry_ = nullptr;
info.address_ = address_;
}
return info;
}
} // namespace spy
#endif

26
MethodInfo.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef ELFSPY_METHODINFO_H
#define ELFSPY_METHODINFO_H
namespace spy
{
/**
* @class MethodInfo
* contains the resolved function address of a method and the address of it's
* vtable entry if the method is virtual
*/
template <typename ReturnType, typename... ArgTypes>
struct MethodInfo
{
union
{
ReturnType (*function_)(ArgTypes...);
void* address_;
};
void** vtable_entry_;
};
} // namespace spy
#endif

34
MethodPointer.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef ELFSPY_METHODPOINTER_H
#define ELFSPY_METHODPOINTER_H
#include <cstddef>
namespace spy
{
/**
* @namespace spy
* @struct MethodPointer
* handle details of a potentially virtual function pointer to a class method
*/
struct MethodPointer
{
union
{
std::size_t index_;
void* pointer_;
};
std::size_t offset_; // to adjust this pointer with
bool is_virtual() const;
};
inline bool MethodPointer::is_virtual() const
{
return index_ & 1UL;
}
} // namespace spy
#endif

105
Profiler.h Normal file
View File

@ -0,0 +1,105 @@
#ifndef ELFSPY_PROFILER_H
#define ELFSPY_PROFILER_H
#include <time.h>
#include "elfspy/Capture.h"
namespace spy
{
/**
* @namespace spy
* @class Profiler
*/
template <typename H, typename ReturnType, typename... ArgTypes>
class Profiler : public Capture<unsigned long long>
{
public:
Profiler(H& hook);
Profiler(const Profiler&) = delete;
Profiler& operator=(const Profiler&) = delete;
Profiler(Profiler&& move) = default;
Profiler& operator=(Profiler& move) = default;
~Profiler();
private:
static ReturnType profile(ArgTypes...);
static ReturnType (*func_)(ArgTypes...);
static Profiler* instance_;
struct Recorder;
friend class Recorder;
inline void add(unsigned long long nanoseconds)
{
this->captures_.push_back(nanoseconds);
}
struct Recorder
{
inline Recorder()
{
clock_gettime(CLOCK_REALTIME, &start_);
}
inline ~Recorder()
{
struct timespec finish;
clock_gettime(CLOCK_REALTIME, &finish);
unsigned long long nanoseconds = 1000000000ULL
* (finish.tv_sec - start_.tv_sec)
+ finish.tv_nsec - start_.tv_nsec;
Profiler::instance_->add(nanoseconds);
}
struct timespec start_;
};
H& hook_;
};
template <typename H, typename ReturnType, typename... ArgTypes>
Profiler<H, ReturnType, ArgTypes...>*
Profiler<H, ReturnType, ArgTypes...>::instance_ = nullptr;
template <typename H, typename ReturnType, typename... ArgTypes>
ReturnType (*Profiler<H, ReturnType, ArgTypes...>::func_)(ArgTypes...)
= nullptr;
template <typename H, typename ReturnType, typename... ArgTypes>
inline Profiler<H, ReturnType, ArgTypes...>::
Profiler(H& hook)
:hook_(hook)
{
func_ = hook_.patch(&Profiler::profile);
instance_ = this;
}
template <typename H, typename ReturnType, typename... ArgTypes>
inline Profiler<H, ReturnType, ArgTypes...>::~Profiler()
{
hook_.patch(func_);
instance_ = nullptr;
}
template <typename H, typename ReturnType, typename... ArgTypes>
ReturnType Profiler<H, ReturnType, ArgTypes...>::profile(ArgTypes... args)
{
Recorder r;
return (*func_)(std::forward<ArgTypes>(args)...);
}
template <typename H>
inline auto profiler(H& hook)
-> typename H::template Export<Profiler, H, typename H::Result>::Type
{
return hook;
}
template <typename H>
inline auto new_profiler(H& hook)
-> typename H::template Export<Profiler, H, typename H::Result>::Type*
{
using Install =
typename H::template Export<Profiler, H, typename H::Result>::Type;
return new Install(hook);
}
} // namespace elfspy
#endif

1
README Normal file
View File

@ -0,0 +1 @@
C++ Testing in isolation using fakes and spies

29
Report.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "elfspy/Report.h"
#include <cerrno>
#include <cstring>
#include "elfspy/Error.h"
namespace spy
{
Report::Report()
{
error_ = errno;
}
Report::~Report()
{
show();
}
void Report::show()
{
if (error_)
{
*this << ": " << std::strerror(error_);
}
error(str().c_str());
}
} // namespace spy

24
Report.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef ELFSPY_REPORT_H
#define ELFSPY_REPORT_H
#include <sstream>
namespace spy
{
class Report : public std::ostringstream
{
public:
Report();
~Report();
protected:
void show();
private:
int error_;
};
} // namespace spy
#endif

47
Result.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef ELFSPY_RESULT_H
#define ELFSPY_RESULT_H
#include "elfspy/Variadic.h"
#include "elfspy/Thunk.h"
#include "elfspy/ThunkHandle.h"
#include "elfspy/Capture.h"
namespace spy
{
/**
* @namespace spy
* @class Result
* This captures the return value of an invocation
*/
template <typename ReturnType>
class Result : public Thunk<ReturnType&&>,
public Capture<ReturnType>
{
public:
virtual void invoke(ReturnType&& result) override;
};
template <typename ReturnType>
void Result<ReturnType>::invoke(ReturnType&& result)
{
// forward arguments to avoid copying
this->captures_.push_back(std::forward<ReturnType>(result));
}
template <typename H>
inline auto result(H& hook) -> ThunkHandle<Result<typename H::Result>>
{
return hook.return_thunks();
}
template <typename H>
inline auto new_result(H& hook) -> ThunkHandle<Result<typename H::Result>>*
{
return new ThunkHandle<typename H::Result>(hook.return_thunks());
}
} // namespace elfspy
#endif

13
SPY.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "elfspy/SPY.h"
#include "elfspy/GOTEntry.h"
namespace spy
{
void initialise(int argc, char** argv)
{
GOTEntry::initialise(argc, argv);
}
} // namespace spy

141
SPY.h Normal file
View File

@ -0,0 +1,141 @@
#ifndef ELFSPY_SPY_H
#define ELFSPY_SPY_H
#include "elfspy/Hook.h"
#include "elfspy/Method.h"
namespace spy
{
void initialise(int argc, char** argv);
namespace impl
{
/**
* @namespace spy
* @namespace impl
* class Function
* A Hook for functions. Uses curiously recurring template pattern to create a
* unique type for each function pointer it is instantiated with
*/
template <typename R, typename... Args>
struct Function
{
// use CRTP as Hook uses static members
template <R (*FUNC)(Args...)>
struct Instance : public Hook<Instance<FUNC>, R, Args...>
{
using Base = Hook<Instance<FUNC>, R, Args...>;
Instance() : Base(FUNC) { }
};
};
/**
* @namespace spy
* @namespace impl
* class MutableMethod
* A Hook for non const methods. Uses curiously recurring template pattern to
* create a unique type for each method pointer it is instantiated with
*/
template <typename R, typename T, typename... Args>
struct MutableMethod
{
template <R (T::*METHOD)(Args...)>
struct Instance : public Hook<Instance<METHOD>, R, T*, Args...>
{
using Base = Hook<Instance<METHOD>, R, T*, Args...>;
Instance() : Base(Method<R, T, Args...>(METHOD).resolve()) { }
};
};
/**
* @namespace spy
* @namespace impl
* class ConstMethod
* A Hook for const methods. Uses curiously recurring template pattern to
* create a unique type for each method pointer it is instantiated with
*/
template <typename R, typename T, typename... Args>
struct ConstMethod
{
template <R (T::*METHOD)(Args...) const>
struct Instance : public Hook<Instance<METHOD>, R, T*, Args...>
{
using Base = Hook<Instance<METHOD>, R, T*, Args...>;
Instance() : Base(Method<R, T, Args...>(METHOD).resolve()) { }
};
};
/**
* @namespace spy
* @namespace impl
* class VolatileMethod
* A Hook for volatile methods. Uses curiously recurring template pattern to
* create a unique type for each method pointer it is instantiated with
*/
template <typename R, typename T, typename... Args>
struct VolatileMethod
{
template <R (T::*METHOD)(Args...) volatile>
struct Instance : public Hook<Instance<METHOD>, R, T*, Args...>
{
using Base = Hook<Instance<METHOD>, R, T*, Args...>;
Instance() : Base(Method<R, T, Args...>(METHOD).resolve()) { }
};
};
/**
* @namespace spy
* @namespace impl
* class ConstVolatileMethod
* A Hook for volatile methods. Uses curiously recurring template pattern to
* create a unique type for each method pointer it is instantiated with
*/
template <typename R, typename T, typename... Args>
struct ConstVolatileMethod
{
template <R (T::*METHOD)(Args...) const volatile>
struct Instance : public Hook<Instance<METHOD>, R, T*, Args...>
{
using Base = Hook<Instance<METHOD>, R, T*, Args...>;
Instance() : Base(Method<R, T, Args...>(METHOD).resolve()) { }
};
};
/**
* @brief
* overloaded function only to get the right decltype depending on the
* argument passed for the SPY macro. Hence no definition is provided.
*/
template <typename R, typename... Args>
auto create(R (*)(Args...)) -> Function<R, Args...>;
template <typename R, typename T, typename... Args>
auto create(R (T::*)(Args...)) -> MutableMethod<R, T, Args...>;
template <typename R, typename T, typename... Args>
auto create(R (T::*)(Args...) const) -> ConstMethod<R, T, Args...>;
template <typename R, typename T, typename... Args>
auto create(R (T::*)(Args...) volatile) -> VolatileMethod<R, T, Args...>;
template <typename R, typename T, typename... Args>
auto create(R (T::*)(Args...) const volatile)
-> ConstVolatileMethod<R, T, Args...>;
} // namespace impl
} // namespace spy
/**
* @def SPY(F)
* Macro SPY captures signature of F whether F is a function or a method, and
* injects a Hook for F into the Global Offset Table which is invoked between a
* program's calls to F and the invocations of F.
* SPY returns a (derived) instance of Hook that can be used to add Hooks to.
*/
#define SPY(F) decltype(spy::impl::create(F))::Instance<F>()
#endif

28
Thunk.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef ELFSPY_THUNK_H
#define ELFSPY_THUNK_H
namespace spy
{
/**
* @namespace spy
* @class Thunk
* Base class for Thunks that can be created by Hook
* No return value type with a Thunk to avoid interfering with the value
* signature is given by ArgTypes... and is either empty or the same as the
* Thunk's function
*/
template <typename... ArgTypes>
class Thunk
{
public:
typedef Thunk<ArgTypes...> Base;
virtual ~Thunk() = default;
virtual void invoke(ArgTypes&&... args) = 0;
};
} // namespace spy
#endif

94
ThunkHandle.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef ELFSPY_THUNKHANDLE_H
#define ELFSPY_THUNKHANDLE_H
#include <vector>
namespace spy
{
/**
* @namespace spy
* @class ThunkHandle
* This holds the return from a Thunk's creation function. The Thunk itself is
* a base class. ThunkHandle registers/deregisters the Thunk to/from the Hook
*/
template <typename ThunkType>
class ThunkHandle : public ThunkType
{
public:
/**
* Register thunk member to hook and construct with optional arguments
* @param thunks container to register/deregister Thunk from
* @param construction arguments (if any) to pass to ThunkType constructor
*/
template <typename... C>
ThunkHandle(std::vector<typename ThunkType::Base*>& thunks,
C&&... construction);
ThunkHandle(const ThunkHandle&) = delete;
ThunkHandle& operator=(const ThunkHandle&) = delete;
ThunkHandle(ThunkHandle&& move);
ThunkHandle& operator=(ThunkHandle&& move);
/**
* deregister from hook
*/
~ThunkHandle();
private:
std::vector<typename ThunkType::Base*>& thunks_;
void deregister();
};
template <typename ThunkType>
template <typename... C>
inline ThunkHandle<ThunkType>::
ThunkHandle(std::vector<typename ThunkType::Base*>& thunks,
C&&... construction)
:ThunkType(std::forward<C>(construction)...)
,thunks_(thunks)
{
// register the thunk
thunks_.push_back(this);
}
template <typename ThunkType>
void ThunkHandle<ThunkType>::deregister()
{
// find and deregister the thunk
for (auto i = thunks_.begin(); i != thunks_.end(); ++i) {
if (*i == this) {
thunks_.erase(i);
break;
}
}
}
template <typename ThunkType>
inline ThunkHandle<ThunkType>::ThunkHandle(ThunkHandle&& move)
:ThunkType(move)
,thunks_(move.thunks_)
{
move.deregister();
thunks_.push_back(this);
}
template <typename ThunkType>
inline
ThunkHandle<ThunkType>& ThunkHandle<ThunkType>::operator=(ThunkHandle&& move)
{
// thunks_ already points to the correct static member of the the ThunkType
// as it has the function pointer in the template arguments
move.deregister();
thunks_.push_back(this);
return *this;
}
template <typename ThunkType>
inline ThunkHandle<ThunkType>::~ThunkHandle()
{
deregister();
}
} // namespace spy
#endif

38
Variadic.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef ELFSPY_VARIADIC_H
#define ELFSPY_VARIADIC_H
namespace spy
{
/**
* @namespace spy
* @class Variadic
* get the n'th argument from a variadic parameter list
*/
template <size_t N, typename T, typename... ArgTypes> struct Variadic;
template <typename T, typename... ArgTypes>
struct Variadic<0, T, ArgTypes...>
{
typedef T Type;
static T&& get(T&& first, ArgTypes&&...)
{
return std::forward<Type>(first);
}
};
template <size_t N, typename T, typename... ArgTypes>
struct Variadic
{
using Next = Variadic<N - 1, ArgTypes...>;
using Type = typename Next::Type;
static Type&& get(T&& first, ArgTypes&&... args)
{
return std::forward<Type>(Next::get(std::forward<ArgTypes>(args)...));
}
};
} // namespace elfspy
#endif

53
demo.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "elfspy/demo.h"
#include <iostream>
int f()
{
int rv = 0;
rv += add(1, 2);
rv += add(3, 4);
return rv;
}
int add(int a, int b)
{
std::cout << "add(" << a << ", " << b << ") -> " << a + b << std::endl;
return a + b;
}
int sub(int a, int b)
{
std::cout << "sub(" << a << ", " << b << ") -> " << a - b << std::endl;
return a - b;
}
void g()
{
std::cout << "g()" << std::endl;
}
void g(int n)
{
std::cout << "g(" << n << ")" << std::endl;
}
MyClass::~MyClass()
{
}
void MyClass::method(int n)
{
std::cout << "MyClass::method(" << n << ")" << std::endl;
}
void MyClass::virtual_method()
{
std::cout << "MyClass::virtual_method" << std::endl;
}
void MyDerivedClass::virtual_method()
{
method(2);
std::cout << "MyDerivedClass::virtual_method" << std::endl;
}

26
demo.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef ELFSPY_DEMO_H
#define ELFSPY_DEMO_H
#include <iostream>
int add(int a, int b);
int sub(int a, int b);
int f();
void g();
void g(int);
class MyClass
{
public:
virtual ~MyClass();
void method(int n);
virtual void virtual_method();
};
class MyDerivedClass : public MyClass
{
public:
virtual void virtual_method();
};
#endif

105
example.cpp Normal file
View File

@ -0,0 +1,105 @@
#include <cassert>
#include <cstring>
#include <iostream>
#include <time.h>
#include <unistd.h>
#include "elfspy/SPY.h"
#include "elfspy/Call.h"
#include "elfspy/Arg.h"
#include "elfspy/Result.h"
#include "elfspy/Fake.h"
#include "elfspy/Profiler.h"
//#include "elfspy/StackTrace.h"
//#include "elfspy/StackFrame.h"
#include "elfspy/demo.h"
int main(int argc, char** argv)
{
// initialise
spy::initialise(argc, argv);
{
// add some spies about things that happen in f()
// auto f_call = spy::call(SPY(&f)); // nicer
// auto f_call = spy::call(SPY(&f));
auto add_spy = SPY(&add);
auto add_arg0 = spy::arg<0>(add_spy);
auto add_arg1 = spy::arg<1>(add_spy);
auto add_call = spy::call(add_spy);
auto add_result = spy::result(add_spy);
//auto add_stack = spy::stack_trace(add_spy);
int rv = f();
assert(rv == 10);
// assert(f_call->count() == 1);
assert(add_call.count() == 2);
assert(add_arg0.value(0) == 1);
assert(add_arg1.value(0) == 2);
assert(add_arg0.value(1) == 3);
assert(add_arg1.value(1) == 4);
assert(add_result.value(0) == 3);
assert(add_result.value(1) == 7);
}
{
auto add_spy = SPY(&add);
auto add_patch = spy::fake(add_spy, &sub);
int rv = f();
assert(rv == -2);
}
{
auto add_spy = SPY(&add);
auto lambda = [](int a, int b) { return a - b; };
auto add_patch = spy::fake(add_spy, lambda);
int rv = f();
assert(rv == -2);
}
int rv = f();
assert(rv == 10);
{
time_t time_diff = 0;
auto time_spy = SPY(&time);
auto time_changer = [&time_diff, &time_spy](time_t* tloc) -> time_t {
time_t fake_time = time_spy.invoke_real(tloc) + time_diff;
if (tloc) {
*tloc = fake_time;
}
return fake_time;
};
auto time_fake = spy::fake(time_spy, time_changer);
// relive Y2K
struct tm false_time;
memset(&false_time, 0, sizeof(false_time));
false_time.tm_sec = 58;
false_time.tm_min = 59;
false_time.tm_hour = 23;
false_time.tm_mday = 31;
false_time.tm_mon = 11;
false_time.tm_year = 1999 - 1900;
false_time.tm_isdst = -1;
time_t in_the_past = mktime(&false_time);
time_t now = time(nullptr);
time_diff = in_the_past - now; // set the time_changer difference
time_t reported_time = time(nullptr);
std::cout << ctime(&reported_time) << std::endl;
sleep(4);
reported_time = time(nullptr);
std::cout << ctime(&reported_time) << std::endl;
}
#if 0
assert(add_stack->value().contains(SPY(&f)));
for (spy::StackFrame frame : add_stack->value()) {
std::cout << frame.name() << std::endl;
}
#endif
{
auto add_spy = SPY(&add);
auto add_profiler = spy::profiler(add_spy);
int rv = f();
int run = 0;
for (auto duration : add_profiler) {
std::cout << ++run << ": " << duration << " nanoseconds" << std::endl;
}
}
std::cout << "test passed" << std::endl;
return 0;
}

32
example1.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Call.h"
#include "elfspy/Arg.h"
#include "elfspy/Result.h"
#include "elfspy/demo.h"
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
{
// add some spies about things that happen in f()
auto add_spy = SPY(&add);
auto add_arg0 = spy::arg<0>(add_spy); // capture first argument of add()
auto add_arg1 = spy::arg<1>(add_spy); // capture second argument of add()
auto add_call = spy::call(add_spy); // capture number of calls to add()
auto add_result = spy::result(add_spy); // capture return value from add()
int rv = f();
assert(rv == 10);
assert(add_call.count() == 2); // verify add is called twice
assert(add_arg0.value(0) == 1); // verify first argument of first call
assert(add_arg1.value(0) == 2); // verify second argument of first call
assert(add_arg0.value(1) == 3); // verify first argument of second call
assert(add_arg1.value(1) == 4); // verify second argument of second call
assert(add_result.value(0) == 3); // verify return value of first call
assert(add_result.value(1) == 7); // verify return value of second call
}
return 0;
}

26
example2.cpp Normal file
View File

@ -0,0 +1,26 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Fake.h"
#include "elfspy/demo.h"
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
auto add_spy = SPY(&add);
{
auto add_fake = spy::fake(add_spy, &sub);
int rv = f();
assert(rv == -2);
}
int rv = f();
assert(rv == 10);
{
auto multiply = [](int a, int b) { return a * b; };
auto add_fake = spy::fake(add_spy, multiply);
int rv = f();
assert(rv == 14);
}
return 0;
}

41
example3.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "elfspy/SPY.h"
#include "elfspy/Fake.h"
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
time_t time_diff = 0;
auto time_spy = SPY(&time);
auto time_changer = [&time_diff, &time_spy](time_t* tloc) -> time_t {
time_t fake_time = time_spy.invoke_real(tloc) + time_diff; // call time(time_t*) in libc
if (tloc) {
*tloc = fake_time;
}
return fake_time;
};
auto time_fake = spy::fake(time_spy, time_changer);
// relive Y2K
struct tm false_time;
memset(&false_time, 0, sizeof(false_time));
false_time.tm_sec = 58;
false_time.tm_min = 59;
false_time.tm_hour = 23;
false_time.tm_mday = 31;
false_time.tm_mon = 11;
false_time.tm_year = 1999 - 1900;
false_time.tm_isdst = -1;
time_t in_the_past = mktime(&false_time);
time_t now = time(nullptr);
time_diff = in_the_past - now; // set the time_changer difference
time_t reported_time = time(nullptr);
std::cout << ctime(&reported_time) << std::endl;
sleep(4);
reported_time = time(nullptr);
std::cout << ctime(&reported_time) << std::endl;
return 0;
}

20
example5.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Profiler.h"
#include "elfspy/demo.h"
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
auto add_spy = SPY(&add);
auto add_profiler = spy::profiler(add_spy);
int rv = f();
int run = 0;
for (auto duration : add_profiler) {
std::cout << ++run << ": " << duration << " nanoseconds" << std::endl;
}
return 0;
}

43
example6.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Arg.h"
#include "elfspy/Fake.h"
#include "elfspy/demo.h"
void func(MyClass*, int n)
{
std::cout << "func(" << n << ")" << std::endl;
}
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
auto method_spy = SPY(&MyClass::method);
MyClass my_object;
{
auto method_this = spy::arg<0>(method_spy);
auto method_arg0 = spy::arg<1>(method_spy);
auto method_fake = spy::fake(method_spy, &func);
my_object.method(117);
assert(method_this.value() == &my_object);
assert(method_arg0.value() == 117);
}
my_object.method(314);
{
auto method_this = spy::arg<0>(method_spy);
auto method_arg0 = spy::arg<1>(method_spy);
auto lambda =
[](MyClass* object, int n)
{
std::cout << "fake(" << n << ")" << std::endl;
};
auto method_fake = spy::fake(method_spy, lambda);
my_object.method(256);
assert(method_this.value() == &my_object);
assert(method_arg0.value() == 256);
}
return 0;
}

31
example7.cpp Normal file
View File

@ -0,0 +1,31 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Arg.h"
#include "elfspy/Fake.h"
#include "elfspy/demo.h"
void func(MyClass*)
{
std::cout << "func()" << std::endl;
}
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
auto method_spy = SPY(&MyClass::virtual_method);
MyClass my_object;
auto method_this = spy::arg<0>(method_spy);
auto method_fake = spy::fake(method_spy, &func);
my_object.virtual_method();
assert(method_this.value() == &my_object);
MyClass* my_derived_object = new MyDerivedClass;
my_derived_object->virtual_method();
assert(method_this.size() == 1); // no new value captured
MyClass* my_heap_object = new MyClass;
my_heap_object->virtual_method();
assert(method_this.value(1) == my_heap_object);
return 0;
}

32
example9.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <cassert>
#include <iostream>
#include "elfspy/SPY.h"
#include "elfspy/Fake.h"
#include "elfspy/demo.h"
void g1_replace(int n);
void g2_replace();
int main(int argc, char** argv)
{
spy::initialise(argc, argv);
auto g_int_spy = SPY((void (*)(int))&g);
auto g_spy = SPY((void (*)())&g);
auto g_int_fake = spy::fake(g_int_spy, &g1_replace);
auto g_fake = spy::fake(g_spy, &g2_replace);
g(1);
g();
return 0;
}
void g1_replace(int n)
{
std::cout << "g1_replace(" << n << ")" << std::endl;
}
void g2_replace()
{
std::cout << "g2_replace()" << std::endl;
}

56
makefile Normal file
View File

@ -0,0 +1,56 @@
.SECONDEXPANSION:
LIBRARIES := elfspy demo
BINARIES := example1 example2 example3 example5 example6 example7 example9
elfspy_OBJS := \
Error.o \
Report.o \
Fail.o \
MFile.o \
ELFInfo.o \
GOTEntry.o \
SPY.o \
Call.o
demo_OBJS := \
demo.o
example1_LIBRARIES := elfspy demo
example2_LIBRARIES := elfspy demo
example3_LIBRARIES := elfspy demo
example5_LIBRARIES := elfspy demo
example6_LIBRARIES := elfspy demo
example7_LIBRARIES := elfspy demo
example9_LIBRARIES := elfspy demo
all : $(BINARIES)
CXXFLAGS := -I.. -std=c++14 -g -fPIC -O0
GCLIB_PATH := /home/robin/glibc/2.23/bin \
GCLIB_LIBPATH := $(GCLIB_PATH)/lib \
DYNAMIC_DEBUG := \
-L$(GCLIB_LIBPATH) \
-Wl,--rpath=$(GCLIB_LIBPATH) \
-Wl,--dynamic-linker=$(GCLIB_LIBPATH)/ld-linux-x86-64.so.2
BIND_ALL := -z now
LD_FLAGS := # $(BIND_ALL)
#LD_FLAGS := $(DYNAMIC_DEBUG) -rdynamic -ldl
%.o : %.cpp
g++ $(CXXFLAGS) -c $< -o $@
$(patsubst %,lib%.so,$(LIBRARIES)) : $$($$(patsubst lib%.so,%,$$@)_OBJS)
g++ $(CXXFLAGS) -shared $^ -o $@ $(LD_FLAGS)
readelf -Wa $@ | c++filt > $(@:%.so=%.elf)
$(BINARIES) : $$(@).cpp $$(patsubst %,lib%.so,$$($$@_LIBRARIES))
g++ $(CXXFLAGS) $(@).cpp -o $@ $(LD_FLAGS) -L. $(patsubst %,-l%,$($@_LIBRARIES)) -rdynamic -ldl
readelf -Wa $@ | c++filt > $(@).elf
clean :
rm -f *.o *.so $(BINARIES) *.elf