# cpp-stub **Repository Path**: clauszy/cpp-stub ## Basic Information - **Project Name**: cpp-stub - **Description**: C++ 单元测试打桩 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: https://github.com/coolxv/cpp-stub - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 16 - **Created**: 2020-11-25 - **Last Updated**: 2024-06-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [中文](README_zh.md)|[English](README.md) # 原理介绍 ## 两个核心点 - 如何获取原函数的地址(**addr_pri.h**、**addr_any.h**) - 如何用桩函数替换原函数(**stub.h**) ## 一些说明 - stub.h(适合 windows, linux) 基于C++98开发; 使用 inline hook 技术; 主要解决函数替换问题 (相关参考:[stub](https://github.com/3gguan/stub.git)) - addr_pri.h(适合 windows, linux) 基于C++11开发; 主要解决对象的私有方法地址获取问题 (相关参考:[access_private](https://github.com/martong/access_private)) - src_linux/addr_any.h(仅适合 linux) 基于C++98开发; 使用 elfio 库查询ELF格式文件的符号表获取函数的地址 (也可以使用 bfd 库); 主要解决静态函数地址获取问题,前提编译时得包含调试信息 (相关参考:[ELFIO](https://github.com/serge1/ELFIO)、[bfd](https://sourceware.org/binutils/docs/bfd/)) - src_win/addr_any.h(仅适合 windows) 基于C++98开发; 使用 dbghelp 库查询PDB文件的符号表获取函数的地址; 主要解决静态函数地址获取问题,前提编译时得包含调试信息 (相关参考:[symbol-files](https://docs.microsoft.com/zh-cn/windows/desktop/Debug/symbol-files)、[dbghelpexamples](http://www.debuginfo.com/examples/dbghelpexamples.html)、[pelib](http://www.pelib.com/index.php)) - 使用时linux和windows还是有差别的, 主要涉及桩函数的写法, 还有原函数地址获取的方法不同; 获取虚函数的地址方法就不同,主要是C++ABI不兼容, 编译器支持不同(相关参考: [cxx-abi](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable)) - 支持的操作系统 : windows,linux - 支持的硬件平台 : x86,x86-64,arm64,arm32 - 支持的编译器 : msvc,gcc,clang - 未来计划支持 macOS ## GOT/PLT Hook 、 Trap Hook 对比 Inline Hook | | GOT/PLT Hook | Trap Hook | Inline Hook | | --- | --- | --- | --- | | 实现原理 | 修改延时绑定表 | SIGTRAP断点信号 | 运行时指令替换 | | 粒度 | 方法级 | 指令级 | 指令级 | | 作用域 | 窄 | 广 | 广 | | 性能 | 高 | 低 | 高 | | 难度 | 中 | 中 | 极高 | - Inline hook ![](pic/inline.png) - GOT/PLT hook ![](pic/pltgot.png) - Trap hook 陷阱就是用户态的异常,比如除灵零操作和访问无效内存等,还有系统调用也是。 [ptrace](https://man7.org/linux/man-pages/man2/ptrace.2.html) [Backtrace](https://www.gnu.org/software/libc/manual/html_node/Backtraces.html) [Signal](https://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html) [Windows SEH](https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=vs-2019) [Linux EH](http://wiki.dwarfstd.org/index.php?title=Exception_Handling) [Linux SEH](https://gcc.gnu.org/wiki/WindowsGCCImprovements) [VEH and INT3 for windows](https://gist.github.com/coolxv/713f3cc6d013ad49c1a01804f24036d2) [Signal、Backtrace and INT3 for linux](https://gist.github.com/coolxv/22e92aa307cd9346fb6172385fb23fa8) ## X86/X64 跳转指令 ![](pic/intel.png) ## Aarch32/Aarch64 跳转指令 ![](pic/arm32.png) ![](pic/arm64.png) # 单元测试相关说明 ## 不能打桩 - 不能对 exit 函数打桩,编译器做优化了 - 不能对纯虚函数打桩, 纯虚函数没有地址 - 不能对 lambda 函数打桩, lambda 函数获取不到地址 - 不能对静态函数打桩, 静态函数地址不可见.(但可以尝试使用 addr_any.h 接口获取地址) ## 测试替身(来自网络) - 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. ## 单元测试框架 - 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 ## 用例自动生成工具 - [RamFuzz](https://github.com/dekimir/RamFuzz) - [api-sanity-checker](https://github.com/lvc/api-sanity-checker) - [deepstate](https://github.com/trailofbits/deepstate) - [Wings](http://www.codewings.net/) - [C++test](https://www.parasoft.com/products/ctest/) ## 单元测试编译选项, linux g++可用的 - -fno-access-control - -fno-inline - -Wno-pmf-conversions - -Wl,--allow-multiple-definition - -no-pie -fno-stack-protector - -fprofile-arcs - -ftest-coverage ## 代码覆盖率, 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 ``` # 接口介绍 ## stub.h ``` Stub stub stub.set(addr, addr_stub) stub.reset(addr) ``` ## addr_pri.h ``` 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(); ``` ## addr_any.h(linux) ``` AddrAny any //for exe AddrAny any(libname) //for lib int get_local_func_addr_symtab(std::string func_name_regex_str, std::map& result) int get_global_func_addr_symtab(std::string func_name_regex_str, std::map& result) int get_weak_func_addr_symtab(std::string func_name_regex_str, std::map& result) int get_global_func_addr_dynsym( std::string func_name_regex_str, std::map& result) int get_weak_func_addr_dynsym(std::string func_name_regex_str, std::map& result) ``` ## addr_any.h(windows) ``` AddrAny any //for all int get_func_addr(std::string func_name, std::map& result) ``` ## addr_any.h(darwin) ``` not implement ``` # 接口使用示例 ## 常规函数 ``` //for linux and windows #include #include "stub.h" using namespace std; int foo(int a) { cout<<"I am foo"< #include #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"< #include "stub.h" using namespace std; class A{ int i; public: int foo(int a){ cout<<"I am A_foo"< #include "stub.h" using namespace std; class A{ int i; public: static int foo(int a){ cout<<"I am A_foo"< #include "stub.h" using namespace std; class A{ public: template int foo(T a) { cout<<"I am A_foo"< #include "stub.h" using namespace std; class A{ public: template int foo(T a) { cout<<"I am A_foo"< #include "stub.h" using namespace std; class A{ int i; public: int foo(int a){ cout<<"I am A_foo_int"< #include "stub.h" using namespace std; class A{ int i; public: int foo(int a){ cout<<"I am A_foo_int"< #include "stub.h" using namespace std; class A{ public: virtual int foo(int a){ cout<<"I am A_foo"< #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, msvc x64位不支持内联汇编语法, 可以试下单独汇编文件获取 https://docs.microsoft.com/en-us/cpp/assembler/inline/inline-assembler?view=vs-2019 ``` ``` //for clang, the clang++ 暂时没找到支持获取虚函数地址的扩展语法 ``` ## 虚函数并且重载 ``` //for linux gcc #include #include "stub.h" using namespace std; class A{ int i; public: virtual int foo(int a){ cout<<"I am A_foo"< #include "stub.h" using namespace std; class Foo { public: void operator() (int a) { cout<<"I am foo"< #include "stub.h" using namespace std; class Foo { public: void operator() (int a) { cout<<"I am foo"< #include #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"< #include #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"< #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"< #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"< #include #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 result; any.get_local_func_addr_symtab("^foo()$", result); foo(); Stub stub; std::map::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 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::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 #include #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 result; any.get_func_addr("foo", result); foo(); Stub stub; std::map::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 result; any.get_func_addr("printf", result); foo(); Stub stub; std::map::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; } ```