Stub mainly involves two points
- How to get the original function address (addr_pri.h,addr_any.h,c++ method of obtaining an address)
- How to replace the original function with stub function (stub.h)
Explanation
- stub.h(for windows, linux) related methods based on C++98; use inline hook method; mainly completes the function replacement function (reference:x86-api-hooking-demystified、stub、Redirecting-functions-in-shared-ELF-libraries)
- addr_pri.h(for windows, linux) related methods based on C++11; mainly completes the class's private function address acquisition (reference:access_private)
- src_linux/addr_any.h(only for linux) related methods based on C++98, use the elfio library to query the symbol table (also use bfd parsing, centos:binutils-devel); mainly complete the arbitrary form function address acquisition (reference:ELFIO、bfd)
- src_win/addr_any.h(only for windows) related methods based on C++98, use the dbghelp library to query the symbol table of the pdb file (you can also use the pe library to parse the exported symbols); mainly complete the arbitrary form function address acquisition (reference:symbol-files、dbghelpexamples、pelib)
- The usage of windows and linux will be slightly different, because the methods for getting different types of function addresses are different, and the calling conventions are sometimes different.
- Getting virtual function addresses is complicated.Different compilers have different methods of obtaining(reference: cxx-abi)
- Supported operating systems : windows,linux
- Supported hardware platform : x86,x86-64
- Supported compiler : msvc,gcc,clang
- Future plans support macOS and ARM
Cannot stub
- Can't stub the exit function, the compiler has made special optimizations.
- Can't stub pure virtual functions, pure virtual functions not have the address.
- Can't stub lambda functions, lambda functions not get the address.
- Can't stub static functions, static function address is not visible.(You can try to use addr_any.h api.)
Test double
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).
- Spy are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- Mock are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.
- Stub provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
Unit test framework
- gtest、gmock https://github.com/google/googletest
- cppunit https://github.com/epronk/cppunit
- catch2 https://github.com/catchorg/Catch2
- Boost.Test https://github.com/boostorg/test
- cpputest https://github.com/cpputest/cpputest
- doctest https://github.com/onqtam/doctest
- kmtest https://github.com/SergiusTheBest/kmtest
- trompeloeil https://github.com/rollbear/trompeloeil
Unit test compilation option for linux g++
- -fno-access-control
- -fno-inline
- -Wno-pmf-conversions
- -Wl,--allow-multiple-definition
- -no-pie -fno-stack-protector
- -fprofile-arcs
- -ftest-coverage
Code coverage statistics for linux g++
lcov -d build/ -z
lcov -d build/ -b ../../src1 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_1.info
lcov -d build/ -b ../../src2 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_2.info
lcov -a ut_1.info -a ut_2.info -o ut.info
genhtml -o report/ --prefix=`pwd` --branch-coverage --function-coverage ut.info
Interface
Stub stub
stub.set(addr, addr_stub)
stub.reset(addr)
Declaration:
ACCESS_PRIVATE_FIELD(ClassName, TypeName, FieldName)
ACCESS_PRIVATE_FUN(ClassName, TypeName, FunName)
ACCESS_PRIVATE_STATIC_FIELD(ClassName, TypeName, FieldName)
ACCESS_PRIVATE_STATIC_FUN(ClassName, TypeName, FunName)
Use:
access_private_field::ClassNameFieldName(object);
access_private_static_field::ClassName::ClassNameFieldName();
call_private_fun::ClassNameFunName(object,parameters...);
call_private_static_fun::ClassName::ClassNameFunName(parameters...);
get_private_fun::ClassNameFunName();
get_private_static_fun::ClassName::ClassNameFunName();
AddrAny any //for exe
AddrAny any(libname) //for lib
int get_local_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_global_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_weak_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_global_func_addr_dynsym( std::string func_name_regex_str, std::map<std::string,void*>& result)
int get_weak_func_addr_dynsym(std::string func_name_regex_str, std::map<std::string,void*>& result)
AddrAny any //for all
int get_func_addr(std::string func_name, std::map<std::string,void*>& result)
not implement
The interface use case
//for linux and windows
#include<iostream>
#include "stub.h"
using namespace std;
int foo(int a)
{
cout<<"I am foo"<<endl;
return 0;
}
int foo_stub(int a)
{
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(foo, foo_stub);
foo(1);
return 0;
}
//for linux and windows
#include<iostream>
#include <stdarg.h>
#include "stub.h"
using namespace std;
double average(int num, ...)
{
va_list valist;
double sum = 0.0;
int i;
va_start(valist, num);
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
va_end(valist);
cout<<"I am foo"<<endl;
return sum/num;
}
double average_stub(int num, ...)
{
va_list valist;
double sum = 0.0;
int i;
va_start(valist, num);
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
va_end(valist);
cout<<"I am foo_stub"<<endl;
return sum/num;
}
int main()
{
cout << "Average of 2, 3, 4, 5 = " << average(4, 2,3,4,5) << endl;
Stub stub;
stub.set(average, average_stub);
cout << "Average of 2, 3, 4, 5 = " << average(4, 2,3,4,5) << endl;
}
//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A a;
a.foo(1);
return 0;
}
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
class B{
public:
int foo_stub(int a){
cout<<"I am foo_stub"<<endl;
return 0;
}
};
int main()
{
Stub stub;
stub.set(ADDR(A,foo), ADDR(B,foo_stub));
A a;
a.foo(1);
return 0;
}
//for linux and windows
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
static int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(int a)
{
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A::foo(1);
return 0;
}
//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
template<typename T>
int foo(T a)
{
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int x)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set((int(A::*)(int))ADDR(A,foo), foo_stub);
A a;
a.foo(5);
return 0;
}
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
template<typename T>
int foo(T a)
{
cout<<"I am A_foo"<<endl;
return 0;
}
};
class B {
public:
int foo_stub(int a) {
cout << "I am foo_stub" << endl;
return 0;
}
};
int main()
{
Stub stub;
stub.set((int(A::*)(int))ADDR(A,foo), ADDR(B, foo_stub));
A a;
a.foo(5);
return 0;
}
//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo_int"<<endl;
return 0;
}
int foo(double a){
cout<<"I am A_foo-double"<<endl;
return 0;
}
};
int foo_stub_int(void* obj,int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub_int"<< a << endl;
return 0;
}
int foo_stub_double(void* obj,double a)
{
A* o= (A*)obj;
cout<<"I am foo_stub_double"<< a << endl;
return 0;
}
int main()
{
Stub stub;
stub.set((int(A::*)(int))ADDR(A,foo), foo_stub_int);
stub.set((int(A::*)(double))ADDR(A,foo), foo_stub_double);
A a;
a.foo(5);
a.foo(1.1);
return 0;
}
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo_int"<<endl;
return 0;
}
int foo(double a){
cout<<"I am A_foo-double"<<endl;
return 0;
}
};
class B{
int i;
public:
int foo_stub_int(int a)
{
cout << "I am foo_stub_int" << a << endl;
return 0;
}
int foo_stub_double(double a)
{
cout << "I am foo_stub_double" << a << endl;
return 0;
}
};
int main()
{
Stub stub;
stub.set((int(A::*)(int))ADDR(A,foo), ADDR(B, foo_stub_int));
stub.set((int(A::*)(double))ADDR(A,foo), ADDR(B, foo_stub_double));
A a;
a.foo(5);
a.foo(1.1);
return 0;
}
gcc extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html
//for linux gcc
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
virtual int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj,int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
typedef int (*fptr)(A*,int);
fptr A_foo = (fptr)(&A::foo); //obtaining an address
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo();
return 0;
}
//for windows x86
#include<iostream>
#include "stub.h"
using namespace std;
class A {
public:
virtual int foo(int a) {
cout << "I am A_foo" << endl;
return 0;
}
};
class B {
public:
int foo_stub(int a)
{
cout << "I am foo_stub" << endl;
return 0;
}
};
int main()
{
unsigned long addr;
_asm {mov eax, A::foo}
_asm {mov addr, eax}
Stub stub;
stub.set(addr, ADDR(B, foo_stub));
A a;
a.foo(1);
return 0;
}
//for windows x64, the visual studio compiler does not support inline assembly. There are solutions to search for yourself.
//for clang, the clang++ compiler does not support to get virtual function address.
//for linux gcc
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
virtual int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
virtual int foo(double a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
typedef int (*fptr)(A*,int);
fptr A_foo = (fptr)((int(A::*)(int))&A::foo);
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo(1);
return 0;
}
//for linux gcc
#include<iostream>
#include "stub.h"
using namespace std;
class Foo
{
public:
void operator() (int a)
{
cout<<"I am foo"<<endl;
}
};
int foo_stub(void* obj, int a)
{
Foo* o= (Foo*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(Foo,operator()), foo_stub);
Foo foo;
foo(1);
return 0;
}
//for windows
#include<iostream>
#include "stub.h"
using namespace std;
class Foo
{
public:
void operator() (int a)
{
cout<<"I am foo"<<endl;
}
};
class B{
public:
int foo_stub(int a){
cout<<"I am foo_stub"<<endl;
return 0;
}
};
int main()
{
Stub stub;
stub.set(ADDR(Foo,operator()), ADDR(B,foo_stub));
Foo foo;
foo(1);
return 0;
}
//for linux
//Add the -fno-inline compile option, disable inlining, get the function address.
//for windows
//Add /Ob0 to disable inline expansion.
//for linux
#include<iostream>
#include<cstdio>
#include "stub.h"
using namespace std;
int foo(int a)
{
printf("I am foo\n");
return 0;
}
int printf_stub(const char * format, ...)
{
cout<<"I am printf_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(puts, printf_stub);
foo(1);
return 0;
}
//for windows
#include<iostream>
#include<cstdio>
#include "stub.h"
using namespace std;
int foo(int a)
{
printf("I am foo\n");
return 0;
}
int printf_stub(const char * format, ...)
{
cout<<"I am printf_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(printf, printf_stub);
foo(1);
return 0;
}
//for linux
#include<iostream>
#include "stub.h"
#include "addr_pri.h"
using namespace std;
class A{
int a;
int foo(int x){
cout<<"I am A_foo "<< a << endl;
return 0;
}
static int b;
static int bar(int x){
cout<<"I am A_bar "<< b << endl;
return 0;
}
};
ACCESS_PRIVATE_FIELD(A, int, a);
ACCESS_PRIVATE_FUN(A, int(int), foo);
ACCESS_PRIVATE_STATIC_FIELD(A, int, b);
ACCESS_PRIVATE_STATIC_FUN(A, int(int), bar);
int foo_stub(void* obj, int x)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int bar_stub(int x)
{
cout<<"I am bar_stub"<<endl;
return 0;
}
int main()
{
A a;
auto &A_a = access_private_field::Aa(a);
auto &A_b = access_private_static_field::A::Ab();
A_a = 1;
A_b = 10;
call_private_fun::Afoo(a,1);
call_private_static_fun::A::Abar(1);
auto A_foo= get_private_fun::Afoo();
auto A_bar = get_private_static_fun::A::Abar();
Stub stub;
stub.set(A_foo, foo_stub);
stub.set(A_bar, bar_stub);
call_private_fun::Afoo(a,1);
call_private_static_fun::A::Abar(1);
return 0;
}
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int a;
int foo(int x){
cout<<"I am A_foo "<< a << endl;
return 0;
}
static int b;
static int bar(int x){
cout<<"I am A_bar "<< b << endl;
return 0;
}
};
ACCESS_PRIVATE_FIELD(A, int, a);
ACCESS_PRIVATE_FUN(A, int(int), foo);
ACCESS_PRIVATE_STATIC_FIELD(A, int, b);
ACCESS_PRIVATE_STATIC_FUN(A, int(int), bar);
class B {
public:
int foo_stub(int x)
{
cout << "I am foo_stub" << endl;
return 0;
}
};
int bar_stub(int x)
{
cout<<"I am bar_stub"<<endl;
return 0;
}
int main()
{
A a;
auto &A_a = access_private_field::Aa(a);
auto &A_b = access_private_static_field::A::Ab();
A_a = 1;
A_b = 10;
call_private_fun::Afoo(a,1);
call_private_static_fun::A::Abar(1);
auto A_foo= get_private_fun::Afoo();
auto A_bar = get_private_static_fun::A::Abar();
Stub stub;
stub.set(A_foo, ADDR(B,foo_stub));
stub.set(A_bar, bar_stub);
call_private_fun::Afoo(a,1);
call_private_static_fun::A::Abar(1);
return 0;
}
find function address from symbol table
//for linux
#include<iostream>
#include<cstdio>
#include "stub.h"
#include "addr_any.h"
//This static function can be in another file or in another dynamic library, needed -g -O0 compile
static int foo()
{
printf("I am foo\n");
return 0;
}
int foo_stub()
{
std::cout << "I am foo_stub" << std::endl;
return 0;
}
int printf_stub(const char * format, ...)
{
std::cout<< "I am printf_stub" << std::endl;
return 0;
}
int main(int argc, char **argv)
{
//Get application static function address
{
AddrAny any;
std::map<std::string,void*> result;
any.get_local_func_addr_symtab("^foo()$", result);
foo();
Stub stub;
std::map<std::string,void*>::iterator it;
for (it=result.begin(); it!=result.end(); ++it)
{
stub.set(it->second ,foo_stub);
std::cout << it->first << " => " << it->second << std::endl;
}
foo();
}
//Get dynamic library static function address
{
AddrAny any("libc-2.27.so");// cat /proc/pid/maps
std::map<std::string,void*> result;
#ifdef __clang__
any.get_global_func_addr_dynsym("^printf$", result);
#else
any.get_weak_func_addr_dynsym("^puts", result);
#endif
foo();
Stub stub;
std::map<std::string,void*>::iterator it;
for (it=result.begin(); it!=result.end(); ++it)
{
stub.set(it->second ,printf_stub);
std::cout << it->first << " => " << it->second << std::endl;
}
foo();
}
return 0;
}
//for windows
#include<iostream>
#include<cstdio>
#include "stub.h"
#include "addr_any.h"
using namespace std;
static int foo()
{
printf("I am foo\n");
return 0;
}
int foo_stub()
{
std::cout << "I am foo_stub" << std::endl;
return 0;
}
int printf_stub(const char * format, ...)
{
std::cout<< "I am printf_stub" << std::endl;
return 0;
}
int main(int argc, char **argv)
{
//Get application static function address
{
AddrAny any;
std::map<std::string,void*> result;
any.get_func_addr("foo", result);
foo();
Stub stub;
std::map<std::string,void*>::iterator it;
for (it=result.begin(); it!=result.end(); ++it)
{
stub.set(it->second ,foo_stub);
std::cout << it->first << " => " << it->second << std::endl;
}
foo();
}
//Get dynamic library static function address
{
AddrAny any;
std::map<std::string,void*> result;
any.get_func_addr("printf", result);
foo();
Stub stub;
std::map<std::string,void*>::iterator it;
for (it=result.begin(); it!=result.end(); ++it)
{
stub.set(it->second ,printf_stub);
std::cout << it->first << " => " << it->second << std::endl;
}
foo();
}
return 0;
}