Initial version
This commit is contained in:
commit
a4d59d09f7
51
Arg.h
Normal file
51
Arg.h
Normal 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
11
Call.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "Call.h"
|
||||
|
||||
namespace spy
|
||||
{
|
||||
|
||||
void Call::invoke()
|
||||
{
|
||||
++count_;
|
||||
}
|
||||
|
||||
} // namespace spy
|
46
Call.h
Normal file
46
Call.h
Normal 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
59
Capture.h
Normal 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
311
ELFInfo.cpp
Normal 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
73
ELFInfo.h
Normal 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
19
ELFObject.h
Normal 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
28
Error.cpp
Normal 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
14
Error.h
Normal 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
14
Fail.cpp
Normal 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
17
Fail.h
Normal 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
60
Fake.h
Normal 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
22
Function.h
Normal 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
243
GOTEntry.cpp
Normal 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
94
GOTEntry.h
Normal 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
115
Hook.h
Normal 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
95
HookBase.h
Normal 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
82
HookImpl.h
Normal 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
75
Lambda.h
Normal 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
40
MFile.cpp
Normal 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
52
MFile.h
Normal 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
75
Method.h
Normal 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
26
MethodInfo.h
Normal 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
34
MethodPointer.h
Normal 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
105
Profiler.h
Normal 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
|
29
Report.cpp
Normal file
29
Report.cpp
Normal 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
24
Report.h
Normal 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
47
Result.h
Normal 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
13
SPY.cpp
Normal 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
141
SPY.h
Normal 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
28
Thunk.h
Normal 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
94
ThunkHandle.h
Normal 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
38
Variadic.h
Normal 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
53
demo.cpp
Normal 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
26
demo.h
Normal 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
105
example.cpp
Normal 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
32
example1.cpp
Normal 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
26
example2.cpp
Normal 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
41
example3.cpp
Normal 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
20
example5.cpp
Normal 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
43
example6.cpp
Normal 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
31
example7.cpp
Normal 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
32
example9.cpp
Normal 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
56
makefile
Normal 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
|
Loading…
Reference in New Issue
Block a user