diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..cd2ab0ad --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +# Settings for clang-format 20 +# See: https://releases.llvm.org/20.1.0/tools/clang/docs/ClangFormatStyleOptions.html + +BasedOnStyle: LLVM +ColumnLimit: 120 +UseTab: Always +IndentWidth: 4 +TabWidth: 4 + +BinPackArguments: false +BinPackParameters: OnePerLine +AlignAfterOpenBracket: BlockIndent +BracedInitializerIndentWidth: 4 +FixNamespaceComments: false +KeepEmptyLines: + AtEndOfFile: true \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5607f35..6fa958bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,73 +1,124 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: Win MSVC, Linux GCC, Linux Clang -on: [push, pull_request, workflow_dispatch] +on: [ push, pull_request, workflow_dispatch ] jobs: - build: + build-and-test: runs-on: ${{ matrix.os }} strategy: - # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + # We want to see all failing combinations, not just the first one. fail-fast: false - # Set up a matrix to run the following 3 configurations: - # 1. - # 2. - # 3. - # - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + # This matrix sets up 16 combinations, which are a cross product of: OS, Build Type, Bitness and Compiler + # Then it excludes 4 combinations where OS is Windows and compiler is Clang. It will be included in the future. + # This results in the following 12 combinations: + # windows-debug-32-msvc | windows-debug-64-msvc | windows-release-32-msvc | windows-release-64-msvc + # linux-debug-32-gcc | linux-debug-64-gcc | linux-release-32-gcc | linux-release-64-gcc + # linux-debug-32-clang | linux-debug-64-clang | linux-release-32-clang | linux-release-64-clang matrix: - os: [ubuntu-latest, windows-latest] - build_type: [Release, Debug] - c_compiler: [gcc, clang, cl] - arch: [x86, AMD64, x86_64] + os: [ ubuntu-latest, windows-latest ] + build_type: [ Debug, Release ] + bitness: [ 32, 64 ] + compiler: [ native, clang ] + exclude: + # TODO: Add this in the future + - os: windows-latest + compiler: clang include: + # Windows - os: windows-latest + build_type: Debug + test_bin_path: Debug\\PolyHook_2.exe + + - os: windows-latest + build_type: Release + test_bin_path: Release\\PolyHook_2.exe + + - os: windows-latest + bitness: 32 + arch: -A Win32 + + - os: windows-latest + bitness: 64 + arch: -A x64 + + ## MSVC + - os: windows-latest + compiler: native c_compiler: cl cpp_compiler: cl + + # Linux + - os: ubuntu-latest + test_bin_path: PolyHook_2 + + - os: ubuntu-latest + bitness: 32 + arch: -DCMAKE_C_FLAGS="-m32" -DCMAKE_CXX_FLAGS="-m32" + + - os: ubuntu-latest + bitness: 64 + arch: -DCMAKE_C_FLAGS="-m64" -DCMAKE_CXX_FLAGS="-m64" + + ## GCC - os: ubuntu-latest + compiler: native c_compiler: gcc cpp_compiler: g++ + + ## Clang - os: ubuntu-latest + compiler: clang c_compiler: clang cpp_compiler: clang++ - exclude: - - os: windows-latest - c_compiler: gcc - - os: windows-latest - c_compiler: clang - - os: ubuntu-latest - c_compiler: cl - - os: windows-latest - arch: x86_x64 - - os: ubuntu-latest - arch: AMD64 + + env: + BUILD_DIR: "${{ github.workspace }}/build" steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Set reusable strings - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - id: strings - shell: bash - run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} - -S ${{ github.workspace }} - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + # The default GitHub runner is missing dependencies for cross-compilation + - name: Install 32-bit compiler toolchain + if: ${{ matrix.bitness == '32' && matrix.os == 'ubuntu-latest' }} + run: sudo apt update && sudo apt install gcc-multilib g++-multilib + + - uses: actions/checkout@v5 + with: + submodules: 'recursive' + + - name: Configure CMake for build + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if + # you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ env.BUILD_DIR }} + -S ${{ github.workspace }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + ${{ matrix.arch }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because + # the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }} + + # TODO: Deduplicate this command + - name: Configure CMake for test + run: > + cmake -B ${{ env.BUILD_DIR }} + -S ${{ github.workspace }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + ${{ matrix.arch }} + -DPOLYHOOK_BUILD_DLL="OFF" + + - name: Build tests + id: build-tests + if: ${{ matrix.c_compiler != 'gcc' }} # See PLH_TEST_CALLBACK in TestUtils.hpp for the reason + run: cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }} --target PolyHook_2 + + - name: Run tests + if: ${{ steps.build-tests.outcome != 'skipped' }} + run: ${{ env.BUILD_DIR }}/${{ matrix.test_bin_path }} diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index e2b253d8..993fe0d4 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,6 @@ + - + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 00000000..6a5e2b6c --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + trmp + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 00000000..7c676a3c --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,398 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 64475944..7878e48d 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 628ae845..94a25f7f 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,9 +2,5 @@ - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index de9cfcc0..525a7543 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,16 @@ if(POLYHOOK_BUILD_DLL) endif() else() add_executable(${PROJECT_NAME}) + + target_sources(${PROJECT_NAME} PRIVATE + ${PROJECT_SOURCE_DIR}/UnitTests/TestUtils.hpp + ${PROJECT_SOURCE_DIR}/UnitTests/TestUtils.cpp + ) + + # https://docs.github.com/en/actions/reference/workflows-and-actions/variables + if("$ENV{CI}" STREQUAL "true") + target_compile_definitions(${PROJECT_NAME} PRIVATE PLH_CI) + endif() endif() set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) diff --git a/Catch.hpp b/Catch.hpp index c6239fd2..9b309bdd 100644 --- a/Catch.hpp +++ b/Catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.13.3 - * Generated: 2020-10-31 18:20:31.045274 + * Catch v2.13.10 + * Generated: 2022-10-16 11:01:23.452308 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,7 +15,7 @@ #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 3 +#define CATCH_VERSION_PATCH 10 #ifdef __clang__ # pragma clang system_header @@ -66,13 +66,16 @@ #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -132,9 +135,9 @@ namespace Catch { #endif -// We have to avoid both ICC and Clang, because they try to mask themselves -// as gcc, and we want only GCC in this block -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) @@ -237,9 +240,6 @@ namespace Catch { // Visual C++ #if defined(_MSC_VER) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) - // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) @@ -248,13 +248,18 @@ namespace Catch { # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +# if !defined(__clang__) // Handle Clang masquerading for msvc + // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(__clang__) // Handle Clang masquerading for msvc # if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) # define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR # endif // MSVC_TRADITIONAL + +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) # endif // __clang__ #endif // _MSC_VER @@ -323,7 +328,7 @@ namespace Catch { // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # include - # if __cpp_lib_byte > 0 + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) @@ -1007,34 +1012,34 @@ struct AutoReg : NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) #endif #endif @@ -1047,7 +1052,7 @@ struct AutoReg : NonCopyable { CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ @@ -1069,7 +1074,7 @@ struct AutoReg : NonCopyable { CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ @@ -1110,18 +1115,18 @@ struct AutoReg : NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ @@ -1159,18 +1164,18 @@ struct AutoReg : NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T,__VA_ARGS__) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T,__VA_ARGS__) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\ @@ -1201,7 +1206,7 @@ struct AutoReg : NonCopyable { static void TestFunc() #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, TmplList ) + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, TmplList ) #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ @@ -1234,18 +1239,18 @@ struct AutoReg : NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\ @@ -1286,18 +1291,18 @@ struct AutoReg : NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ @@ -1331,7 +1336,7 @@ struct AutoReg : NonCopyable { void TestName::test() #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, TmplList ) + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, TmplList ) // end catch_test_registry.h // start catch_capture.hpp @@ -3088,7 +3093,7 @@ namespace Detail { Approx operator-() const; template ::value>::type> - Approx operator()( T const& value ) { + Approx operator()( T const& value ) const { Approx approx( static_cast(value) ); approx.m_epsilon = m_epsilon; approx.m_margin = m_margin; @@ -4160,7 +4165,7 @@ namespace Generators { if (!m_predicate(m_generator.get())) { // It might happen that there are no values that pass the // filter. In that case we throw an exception. - auto has_initial_value = next(); + auto has_initial_value = nextImpl(); if (!has_initial_value) { Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); } @@ -4172,6 +4177,11 @@ namespace Generators { } bool next() override { + return nextImpl(); + } + + private: + bool nextImpl() { bool success = m_generator.next(); if (!success) { return false; @@ -5455,6 +5465,8 @@ namespace Catch { } // namespace Catch // end catch_outlier_classification.hpp + +#include #endif // CATCH_CONFIG_ENABLE_BENCHMARKING #include @@ -6339,9 +6351,10 @@ namespace Catch { void writeTestCase(TestCaseNode const& testCaseNode); - void writeSection(std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode); + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); void writeAssertions(SectionNode const& sectionNode); void writeAssertion(AssertionStats const& stats); @@ -6876,7 +6889,7 @@ namespace Catch { } iters *= 2; } - throw optimized_away_error{}; + Catch::throw_exception(optimized_away_error{}); } } // namespace Detail } // namespace Benchmark @@ -6884,6 +6897,7 @@ namespace Catch { // end catch_run_for_at_least.hpp #include +#include namespace Catch { namespace Benchmark { @@ -7054,8 +7068,8 @@ namespace Catch { double b2 = bias - z1; double a1 = a(b1); double a2 = a(b2); - auto lo = std::max(cumn(a1), 0); - auto hi = std::min(cumn(a2), n - 1); + auto lo = (std::max)(cumn(a1), 0); + auto hi = (std::min)(cumn(a2), n - 1); return { point, resample[lo], resample[hi], confidence_level }; } @@ -7124,7 +7138,9 @@ namespace Catch { } template EnvironmentEstimate> estimate_clock_cost(FloatDuration resolution) { - auto time_limit = std::min(resolution * clock_cost_estimation_tick_limit, FloatDuration(clock_cost_estimation_time_limit)); + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration(clock_cost_estimation_time_limit)); auto time_clock = [](int k) { return Detail::measure([k] { for (int i = 0; i < k; ++i) { @@ -7379,8 +7395,6 @@ namespace Catch { template struct ObjectStorage { - using TStorage = typename std::aligned_storage::value>::type; - ObjectStorage() : data() {} ObjectStorage(const ObjectStorage& other) @@ -7423,7 +7437,7 @@ namespace Catch { return *static_cast(static_cast(&data)); } - TStorage data; + struct { alignas(T) unsigned char data[sizeof(T)]; } data; }; } @@ -7771,7 +7785,7 @@ namespace Catch { double sb = stddev.point; double mn = mean.point / n; double mg_min = mn / 2.; - double sg = std::min(mg_min / 4., sb / std::sqrt(n)); + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); double sg2 = sg * sg; double sb2 = sb * sb; @@ -7790,7 +7804,7 @@ namespace Catch { return (nc / n) * (sb2 - nc * sg2); }; - return std::min(var_out(1), var_out(std::min(c_max(0.), c_max(mg_min)))) / sb2; + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; } bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector::iterator first, std::vector::iterator last) { @@ -7933,7 +7947,7 @@ namespace Catch { #if defined(__i386__) || defined(__x86_64__) #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ #elif defined(__aarch64__) - #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #define CATCH_TRAP() __asm__(".inst 0xd43e0000") #endif #elif defined(CATCH_PLATFORM_IPHONE) @@ -7980,86 +7994,58 @@ namespace Catch { // start catch_fatal_condition.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#if defined( CATCH_CONFIG_WINDOWS_SEH ) +#include namespace Catch { - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + // Wrapper for platform-specific fatal error (signals/SEH) handlers + // + // Tries to be cooperative with other handlers, and not step over + // other handlers. This means that unknown structured exceptions + // are passed on, previous signal handlers are called, and so on. + // + // Can only be instantiated once, and assumes that once a signal + // is caught, the binary will end up terminating. Thus, there + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform(); + public: + // Should also have platform-specific implementations as needed FatalConditionHandler(); - static void reset(); ~FatalConditionHandler(); - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; -} // namespace Catch - -#else - -namespace Catch { - struct FatalConditionHandler { - void reset(); + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } }; -} -#endif +} // end namespace Catch // end catch_fatal_condition.h #include @@ -8185,6 +8171,7 @@ namespace Catch { std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; bool m_lastAssertionPassed = false; bool m_shouldReportUnexpected = true; bool m_includeSuccessfulResults; @@ -10057,6 +10044,36 @@ namespace Catch { } // end catch_errno_guard.h +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h #include namespace Catch { @@ -10573,7 +10590,7 @@ namespace Catch { // Extracts the actual name part of an enum instance // In other words, it returns the Blue part of Bikeshed::Colour::Blue StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurence of ":" + // Find last occurrence of ":" size_t name_start = enumInstance.size(); while (name_start > 0 && enumInstance[name_start - 1] != ':') { --name_start; @@ -10735,25 +10752,47 @@ namespace Catch { // end catch_exception_translator_registry.cpp // start catch_fatal_condition.cpp -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif +#include + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { - // Report the error condition + //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } -} -#endif // signals/SEH handling + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked anecdotally, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. @@ -10766,7 +10805,7 @@ namespace Catch { { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -10777,38 +10816,50 @@ namespace Catch { return EXCEPTION_CONTINUE_SEARCH; } + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static PVOID exceptionHandlerHandle = nullptr; + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; + ULONG guaranteeSize = static_cast(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + void FatalConditionHandler::engage_platform() { // Register as first handler in current chain exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); + if (!exceptionHandlerHandle) { + CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); + } } - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; + void FatalConditionHandler::disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); } + exceptionHandlerHandle = nullptr; } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } +} // end namespace Catch -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; +#endif // CATCH_CONFIG_WINDOWS_SEH -} // namespace Catch +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) +#include namespace Catch { @@ -10817,10 +10868,6 @@ namespace Catch { const char* name; }; - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -10830,7 +10877,32 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; - void FatalConditionHandler::handleSignal( int sig ) { +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { char const * name = ""; for (auto const& def : signalDefs) { if (sig == def.id) { @@ -10838,16 +10910,33 @@ namespace Catch { break; } } - reset(); - reportFatal(name); + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { - isSet = true; + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -10859,40 +10948,17 @@ namespace Catch { } } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } + void FatalConditionHandler::disengage_platform() { + restorePreviousSignalHandlers(); } - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; - -} // namespace Catch - -#else - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -#endif // signals/SEH handling +} // end namespace Catch -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif +#endif // CATCH_CONFIG_POSIX_SIGNALS // end catch_fatal_condition.cpp // start catch_generators.cpp @@ -11447,7 +11513,8 @@ namespace { return lhs == rhs; } - auto ulpDiff = std::abs(lc - rc); + // static cast as a workaround for IBM XLC + auto ulpDiff = std::abs(static_cast(lc - rc)); return static_cast(ulpDiff) <= maxUlpDiff; } @@ -11621,7 +11688,6 @@ Floating::WithinRelMatcher WithinRel(float target) { } // namespace Matchers } // namespace Catch - // end catch_matchers_floating.cpp // start catch_matchers_generic.cpp @@ -12955,9 +13021,8 @@ namespace Catch { } void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } void RunContext::handleUnfinishedSections() { @@ -13325,6 +13390,10 @@ namespace Catch { filename.erase(0, lastSlash); filename[0] = '#'; } + else + { + filename.insert(0, "#"); + } auto lastDot = filename.find_last_of('.'); if (lastDot != std::string::npos) { @@ -13487,7 +13556,7 @@ namespace Catch { // Handle list request if( Option listed = list( m_config ) ) - return static_cast( *listed ); + return (std::min) (MaxExitCode, static_cast(*listed)); TestGroup tests { m_config }; auto const totals = tests.execute(); @@ -13530,7 +13599,6 @@ namespace Catch { void addSingleton(ISingleton* singleton ) { getSingletons()->push_back( singleton ); } - void cleanupSingletons() { auto& singletons = getSingletons(); for( auto singleton : *singletons ) @@ -14127,24 +14195,28 @@ namespace Catch { namespace { struct TestHasher { - explicit TestHasher(Catch::SimplePcg32& rng_instance) { - basis = rng_instance(); - basis <<= 32; - basis |= rng_instance(); - } + using hash_t = uint64_t; - uint64_t basis; + explicit TestHasher( hash_t hashSuffix ): + m_hashSuffix{ hashSuffix } {} - uint64_t operator()(TestCase const& t) const { - // Modified FNV-1a hash - static constexpr uint64_t prime = 1099511628211; - uint64_t hash = basis; - for (const char c : t.name) { + uint32_t operator()( TestCase const& t ) const { + // FNV-1a hash with multiplication fold. + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { hash ^= c; hash *= prime; } - return hash; + hash ^= m_hashSuffix; + hash *= prime; + const uint32_t low{ static_cast( hash ) }; + const uint32_t high{ static_cast( hash >> 32 ) }; + return low * high; } + + private: + hash_t m_hashSuffix; }; } // end unnamed namespace @@ -14162,9 +14234,9 @@ namespace Catch { case RunTests::InRandomOrder: { seedRng( config ); - TestHasher h( rng() ); + TestHasher h{ config.rngSeed() }; - using hashedTest = std::pair; + using hashedTest = std::pair; std::vector indexed_tests; indexed_tests.reserve( unsortedTestCases.size() ); @@ -15317,7 +15389,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 13, 3, "", 0 ); + static Version version( 2, 13, 10, "", 0 ); return version; } @@ -16730,6 +16802,7 @@ CATCH_REGISTER_REPORTER("console", ConsoleReporter) #include #include #include +#include namespace Catch { @@ -16757,7 +16830,7 @@ namespace Catch { #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif - return std::string(timeStamp); + return std::string(timeStamp, timeStampSize-1); } std::string fileNameTag(const std::vector &tags) { @@ -16768,6 +16841,17 @@ namespace Catch { return it->substr(1); return std::string(); } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + } // anonymous namespace JunitReporter::JunitReporter( ReporterConfig const& _config ) @@ -16837,7 +16921,7 @@ namespace Catch { if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else - xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "time", formatDuration( suiteTime ) ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write properties if there are any @@ -16882,12 +16966,13 @@ namespace Catch { if ( !m_config->name().empty() ) className = m_config->name() + "." + className; - writeSection( className, "", rootSection ); + writeSection( className, "", rootSection, stats.testInfo.okToFail() ); } - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + '/' + name; @@ -16904,13 +16989,18 @@ namespace Catch { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time", formatDuration( sectionNode.stats.durationInSeconds ) ); // This is not ideal, but it should be enough to mimic gtest's // junit output. // Ideally the JUnit reporter would also handle `skipTest` // events and write those out appropriately. xml.writeAttribute( "status", "run" ); + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } + writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) @@ -16920,9 +17010,9 @@ namespace Catch { } for( auto const& childNode : sectionNode.childSections ) if( className.empty() ) - writeSection( name, "", *childNode ); + writeSection( name, "", *childNode, testOkToFail ); else - writeSection( className, name, *childNode ); + writeSection( className, name, *childNode, testOkToFail ); } void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { @@ -17434,12 +17524,20 @@ namespace Catch { #ifndef __OBJC__ +#ifndef CATCH_INTERNAL_CDECL +#ifdef _MSC_VER +#define CATCH_INTERNAL_CDECL __cdecl +#else +#define CATCH_INTERNAL_CDECL +#endif +#endif + #if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) // Standard C/C++ Win32 Unicode wmain entry point -extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +extern "C" int CATCH_INTERNAL_CDECL wmain (int argc, wchar_t * argv[], wchar_t * []) { #else // Standard C/C++ main entry point -int main (int argc, char * argv[]) { +int CATCH_INTERNAL_CDECL main (int argc, char * argv[]) { #endif return Catch::Session().run( argc, argv ); @@ -17567,9 +17665,9 @@ int main (int argc, char * const argv[]) { #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) #define CATCH_BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) #define CATCH_BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name) #endif // CATCH_CONFIG_ENABLE_BENCHMARKING // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required @@ -17671,9 +17769,9 @@ int main (int argc, char * const argv[]) { #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) #define BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) #define BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name) #endif // CATCH_CONFIG_ENABLE_BENCHMARKING using Catch::Detail::Approx; @@ -17720,8 +17818,8 @@ using Catch::Detail::Approx; #define CATCH_WARN( msg ) (void)(0) #define CATCH_CAPTURE( msg ) (void)(0) -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) #define CATCH_SECTION( ... ) @@ -17730,7 +17828,7 @@ using Catch::Detail::Approx; #define CATCH_FAIL_CHECK( ... ) (void)(0) #define CATCH_SUCCEED( ... ) (void)(0) -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) @@ -17753,8 +17851,8 @@ using Catch::Detail::Approx; #endif // "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className ) #define CATCH_GIVEN( desc ) #define CATCH_AND_GIVEN( desc ) #define CATCH_WHEN( desc ) @@ -17802,10 +17900,10 @@ using Catch::Detail::Approx; #define INFO( msg ) (void)(0) #define UNSCOPED_INFO( msg ) (void)(0) #define WARN( msg ) (void)(0) -#define CAPTURE( msg ) (void)(0) +#define CAPTURE( ... ) (void)(0) -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #define METHOD_AS_TEST_CASE( method, ... ) #define REGISTER_TEST_CASE( Function, ... ) (void)(0) #define SECTION( ... ) @@ -17813,7 +17911,7 @@ using Catch::Detail::Approx; #define FAIL( ... ) (void)(0) #define FAIL_CHECK( ... ) (void)(0) #define SUCCEED( ... ) (void)(0) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) @@ -17843,8 +17941,8 @@ using Catch::Detail::Approx; #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // "BDD-style" convenience wrappers -#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className ) #define GIVEN( desc ) #define AND_GIVEN( desc ) diff --git a/UnitTests/TestUtils.cpp b/UnitTests/TestUtils.cpp new file mode 100644 index 00000000..96d6b3e0 --- /dev/null +++ b/UnitTests/TestUtils.cpp @@ -0,0 +1,15 @@ +#include "./TestUtils.hpp" + +#include "polyhook2/ErrorLog.hpp" + +#include + +namespace PLH::test { + +void registerTestLogger() { + const auto logger = std::make_shared(); + logger->setLogLevel(PLH::ErrorLevel::INFO); + PLH::Log::registerLogger(logger); +} + +} diff --git a/UnitTests/TestUtils.hpp b/UnitTests/TestUtils.hpp new file mode 100644 index 00000000..8ed2d173 --- /dev/null +++ b/UnitTests/TestUtils.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +// clang-format off +/** + * This macro is used to define a hooked function that needs to proxy calls to the original one. + * The main challenge with such a macro is that it needs to perfectly mirror original function signature, + * especially the noexcept attribute. + * This is accomplished by using inner noexcept, which + * tests whether calling the original function with those arguments would be noexcept + * and outer noexcept, which + * uses the result of that test to set the noexcept specification on the lambda. + * In simple terms, this construct says: + * "This lambda is noexcept if and only if calling the original function with the same arguments would be noexcept." + * This is a compile-time mechanism that perfectly mirrors the exception specification of the original function. + * + * Note that this is not supported by GCC, since generic lambdas cannot be assigned to function pointers in GCC. + * Clang allows generic lambdas to decay to function pointers if the instantiated signature matches. + * This is a known divergence from the C++ standard. + */ +#define PLH_TEST_CALLBACK(FUNC, HOOK, TRMP, ...) \ + uint64_t TRMP = 0; \ + decltype(&FUNC) HOOK = [](Args... $args) \ + noexcept(noexcept(std::declval()(std::declval()...))) -> auto { \ + PLH::StackCanary canary; \ + PLH_STOP_OPTIMIZATIONS(); \ + effects.PeakEffect().trigger(); \ + __VA_ARGS__ \ + return PLH::FnCast(TRMP, &FUNC)($args...); \ + } +// clang-format on + +/** + * Most test hooks follow the same convention, + * where hooked functions and trampoline variables derive their name from the original function. + * Hence, it makes sense to create a corresponding macro utility + */ +#define PLH_TEST_DETOUR_CALLBACK(FUNC, ...) PLH_TEST_CALLBACK(FUNC, FUNC##_hooked, FUNC##_trmp, __VA_ARGS__) +#define PLH_TEST_DETOUR(FUNC) detour((uint64_t)&FUNC, (uint64_t)FUNC##_hooked, &FUNC##_trmp); + +/** + * These tests can spontaneously fail if the compiler decides to optimize away + * the handler or inline the function. PLH_NOINLINE attempts to fix the latter, the former + * is out of our control but typically returning volatile things, volatile locals, and a + * printf inside the body can mitigate this significantly. Do serious checking in Debug + * or ReleaseWithDebInfo mode (ReleaseWithDebInfo optimizes _slightly_ less). + */ +#define PLH_STOP_OPTIMIZATIONS() \ + volatile int i = 0; \ + PH_UNUSED(i) + +namespace PLH::test { + +void registerTestLogger(); + +} diff --git a/UnitTests/linux/TestDetourTranslationx64.cpp b/UnitTests/linux/TestDetourTranslationx64.cpp index e69de29b..d4e80042 100644 --- a/UnitTests/linux/TestDetourTranslationx64.cpp +++ b/UnitTests/linux/TestDetourTranslationx64.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include + +#include "polyhook2/Detour/x64Detour.hpp" +#include "polyhook2/PolyHookOsIncludes.hpp" +#include "polyhook2/Tests/StackCanary.hpp" +#include "polyhook2/Tests/TestEffectTracker.hpp" + +#include "../TestUtils.hpp" + +namespace { + EffectTracker effects; +} + +// TODO: Translation + INPLACE scheme + +PLH_TEST_DETOUR_CALLBACK(dlmopen, { + printf("Hooked dlmopen\n"); +}); + +TEST_CASE("Testing Detours with Translations", "[Translation][ADetour]") { + PLH::test::registerTestLogger(); + + SECTION("dlmopen (INPLACE)") { + PLH::StackCanary canary; + + const auto *resultBefore = dlmopen(LM_ID_BASE, LIBM_SO, RTLD_NOW); + + PLH::x64Detour detour((uint64_t)dlmopen, (uint64_t)dlmopen_hooked, &dlmopen_trmp); + // Only INPLACE creates conditions for translation, since + // trampoline will be close to 0x0, where as + // dlmopen will be close to 0x00007F__________ + detour.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE); + REQUIRE(detour.hook()); + + effects.PushEffect(); + const auto *resultAfter = dlmopen(LM_ID_BASE, LIBM_SO, RTLD_NOW); + + REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(resultAfter == resultBefore); + + REQUIRE(detour.unHook()); + } +} diff --git a/UnitTests/linux/TestDetourx64.cpp b/UnitTests/linux/TestDetourx64.cpp index 3be55119..70434476 100644 --- a/UnitTests/linux/TestDetourx64.cpp +++ b/UnitTests/linux/TestDetourx64.cpp @@ -1,7 +1,7 @@ -// -// Created by steve on 7/9/18. -// +// NOLINTBEGIN(*-err58-cpp) + #include + #include "polyhook2/Detour/x64Detour.hpp" #include "polyhook2/ZydisDisassembler.hpp" @@ -10,16 +10,13 @@ #include "polyhook2/PolyHookOsIncludes.hpp" -EffectTracker effects; +#include "../TestUtils.hpp" -/**These tests can spontaneously fail if the compiler desides to optimize away -the handler or inline the function. NOINLINE attempts to fix the latter, the former -is out of our control but typically returning volatile things, volatile locals, and a -printf inside the body can mitigate this significantly. Do serious checking in debug -or releasewithdebinfo mode (relwithdebinfo optimizes sliiiightly less)**/ +EffectTracker effects; NOINLINE void hookMe1() { PLH::StackCanary canary; + std::cout << "hookMe1 called" << std::endl; volatile int var = 1; volatile int var2 = 0; var2 += 3; @@ -30,12 +27,9 @@ NOINLINE void hookMe1() { REQUIRE(var == 2); REQUIRE(var2 == 40); } -uint64_t hookMe1Tramp = NULL; -HOOK_CALLBACK(&hookMe1, h_hookMe1, { - PLH::StackCanary canary; - std::cout << "Hook 1 Called!" << std::endl; - effects.PeakEffect().trigger(); - return PLH::FnCast(hookMe1Tramp, &hookMe1)(); + +PLH_TEST_DETOUR_CALLBACK(hookMe1, { + std::cout << "Hook 1 Called! Trampoline: 0x" << std::hex << hookMe1_trmp << std::endl; }); NOINLINE void hookMe2() { @@ -44,67 +38,74 @@ NOINLINE void hookMe2() { printf("%d\n", i); } } -uint64_t hookMe2Tramp = NULL; -HOOK_CALLBACK(&hookMe2, h_hookMe2, { - PLH::StackCanary canary; + +PLH_TEST_DETOUR_CALLBACK(hookMe2, { std::cout << "Hook 2 Called!" << std::endl; - effects.PeakEffect().trigger(); - return PLH::FnCast(hookMe2Tramp, &hookMe2)(); }); unsigned char hookMe3[] = { -0x57, // push rdi -0x74,0xf9, -0x74, 0xf0,//je 0x0 -0x90, 0x90, 0x90, 0x90, -0x90, 0x90, 0x90, 0x90, -0x90, 0x90, 0x90, 0x90, -0xc3 + 0x57, // push rdi + 0x74, 0xf9, // je -5 + 0x74, 0xf0, // je -14 + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop + 0xc3 // ret }; unsigned char hookMe4[] = { - 0x57, // push rdi - 0x48, 0x83, 0xec, 0x30, //sub rsp, 0x30 - 0x90, 0x90, 0x90, 0x90, - 0x90, 0x90, 0x90, 0x90, - 0x90, 0x90, 0x90, 0x90, - 0x74,0xf2, //je 0x0 - 0xc3 + 0x57, // push rdi + 0x48, 0x83, 0xec, 0x30, // sub rsp, 0x30 + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop + 0x74, 0xf2, // je 0x0 + 0xc3 // ret }; // test call instructions in prologue -unsigned char hookMe5[] = -{ - 0x48, 0x83, 0xEC, 0x28, // 180009240: sub rsp, 28h - 0xE8, 0x96, 0xA8, 0xFF, 0xFF, // call 180003ADF - 0x48, 0x83, 0xC4, 0x28, // add rsp, 28h - 0x48, 0xFF, 0xA0, 0x20, 0x01, 0x00, 0x00 // jmp qword ptr[rax+120h] +unsigned char hookMe5[] = { + 0x48, 0x83, 0xEC, 0x28, // 180009240: sub rsp, 28h + 0xE8, 0x96, 0xA8, 0xFF, 0xFF, // call 180003ADF + 0x48, 0x83, 0xC4, 0x28, // add rsp, 28h + 0x48, 0xFF, 0xA0, 0x20, 0x01, 0x00, 0x00 // jmp qword ptr[rax+120h] }; uint64_t nullTramp = NULL; NOINLINE void h_nullstub() { PLH::StackCanary canary; - volatile int i = 0; - PH_UNUSED(i); + PLH_STOP_OPTIMIZATIONS(); } -uint64_t hookMallocTramp = NULL; -HOOK_CALLBACK(&malloc, h_hookMalloc, { - PLH::StackCanary canary; - volatile int i = 0; - PH_UNUSED(i); - effects.PeakEffect().trigger(); +PLH_TEST_DETOUR_CALLBACK(malloc); - return PLH::FnCast(hookMallocTramp, &malloc)(_args...); -}); +TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]") { + PLH::test::registerTestLogger(); -TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::ZydisDisassembler) { - TestType dis(PLH::Mode::x64); + SECTION("Normal function (VALLOC2)") { + PLH::StackCanary canary; + PLH::x64Detour PLH_TEST_DETOUR(hookMe1); + detour.setDetourScheme(PLH::x64Detour::VALLOC2); + // VALLOC2 is not supported on linux so we expect hooking & unhooking to fail + REQUIRE(detour.hook() == false); + REQUIRE(detour.unHook() == false); + } + SECTION("Normal function (INPLACE)") { + PLH::StackCanary canary; + PLH::x64Detour PLH_TEST_DETOUR(hookMe1); + detour.setDetourScheme(PLH::x64Detour::INPLACE); + REQUIRE(detour.hook() == true); - SECTION("Normal function") { + effects.PushEffect(); + hookMe1(); + REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(detour.unHook() == true); + } + + SECTION("Normal function (CODE_CAVE)") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + PLH::x64Detour PLH_TEST_DETOUR(hookMe1); + detour.setDetourScheme(PLH::x64Detour::CODE_CAVE); REQUIRE(detour.hook() == true); effects.PushEffect(); @@ -113,14 +114,26 @@ TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::ZydisDisa REQUIRE(detour.unHook() == true); } - SECTION("Normal function rehook") - { + SECTION("Normal function (INPLACE_SHORT)") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + PLH::x64Detour PLH_TEST_DETOUR(hookMe1); + detour.setDetourScheme(PLH::x64Detour::INPLACE_SHORT); REQUIRE(detour.hook() == true); - + effects.PushEffect(); - REQUIRE(detour.reHook() == true); // can only really test this doesn't cause memory corruption easily + hookMe1(); + REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(detour.unHook() == true); + } + + SECTION("Normal function rehook") { + PLH::StackCanary canary; + PLH::x64Detour PLH_TEST_DETOUR(hookMe1); + REQUIRE(detour.hook() == true); + + effects.PushEffect(); + REQUIRE(detour.reHook() == true); // can only really test this doesn't + // cause memory corruption easily hookMe1(); REQUIRE(effects.PopEffect().didExecute()); REQUIRE(detour.unHook() == true); @@ -128,7 +141,7 @@ TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::ZydisDisa SECTION("Loop function") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe2, (char*)h_hookMe2, &hookMe2Tramp, dis); + PLH::x64Detour PLH_TEST_DETOUR(hookMe2); REQUIRE(detour.hook() == true); effects.PushEffect(); @@ -139,14 +152,15 @@ TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::ZydisDisa SECTION("Jmp into prol w/src in range") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe3, (char*)&h_nullstub, &nullTramp, dis); + PLH::x64Detour detour((uint64_t)&hookMe3, (uint64_t)&h_nullstub, &nullTramp); + detour.setDetourScheme(PLH::x64Detour::ALL); REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); } SECTION("Jmp into prol w/src out of range") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe4, (char*)&h_nullstub, &nullTramp, dis); + PLH::x64Detour detour((uint64_t)&hookMe4, (uint64_t)&h_nullstub, &nullTramp); REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); @@ -154,23 +168,27 @@ TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::ZydisDisa SECTION("Call instruction early in prologue") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&hookMe5, (char*)&h_nullstub, &nullTramp, dis); + PLH::x64Detour detour((uint64_t)&hookMe5, (uint64_t)&h_nullstub, &nullTramp); REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); } - SECTION("hook malloc") { + SECTION("Hook malloc") { PLH::StackCanary canary; - PLH::x64Detour detour((char*)&malloc, (char*)h_hookMalloc, &hookMallocTramp, dis); - effects.PushEffect(); // catch does some allocations, push effect first so peak works + PLH::x64Detour PLH_TEST_DETOUR(malloc); + effects.PushEffect(); // catch does some allocations, push effect first + // so peak works bool result = detour.hook(); REQUIRE(result == true); - void* pMem = malloc(16); + void *pMem = malloc(16); free(pMem); - detour.unHook(); // unhook so we can popeffect safely w/o catch allocation happening again + detour.unHook(); // unhook so we can popeffect safely w/o catch + // allocation happening again REQUIRE(effects.PopEffect().didExecute()); } } + +// NOLINTEND(*-err58-cpp) diff --git a/UnitTests/linux/TestDetourx86.cpp b/UnitTests/linux/TestDetourx86.cpp index def1cbca..02e463e1 100644 --- a/UnitTests/linux/TestDetourx86.cpp +++ b/UnitTests/linux/TestDetourx86.cpp @@ -1,20 +1,21 @@ -// -// Created by steve on 7/4/17. -// +// NOLINTBEGIN(*-err58-cpp) +#include + #include + #include "polyhook2/Detour/x86Detour.hpp" -#include "polyhook2/ZydisDisassembler.hpp" +#include "polyhook2/Tests/StackCanary.hpp" #include "polyhook2/Tests/TestEffectTracker.hpp" -/**These tests can spontaneously fail if the compiler desides to optimize away -the handler or inline the function. NOINLINE attempts to fix the latter, the former -is out of our control but typically returning volatile things, volatile locals, and a -printf inside the body can mitigate this significantly. Do serious checking in debug -or releasewithdebinfo mode (relwithdebinfo optimizes sliiiightly less)**/ +#include "../TestUtils.hpp" + +namespace { EffectTracker effects; +} + NOINLINE int __cdecl hookMe1() { volatile int var = 1; volatile int var2 = 0; @@ -26,118 +27,108 @@ NOINLINE int __cdecl hookMe1() { return var; } -uint64_t hookMe1Tramp = NULL; -HOOK_CALLBACK(&hookMe1, h_hookMe1, { - std::cout << "Hook 1 Called!" << std::endl; - - effects.PeakEffect().trigger(); - return PLH::FnCast(hookMe1Tramp, &hookMe1)(); +PLH_TEST_DETOUR_CALLBACK(hookMe1, { + std::cout << "Hook 1 Called! Trampoline: 0x" << std::hex << hookMe1_trmp << std::endl; }); -/* 55 push ebp -1: 8b ec mov ebp,esp -3: 74 fb je 0x0 -5: 74 fa je 0x1 -7: 8b ec mov ebp,esp -9: 8b ec mov ebp,esp -b: 8b ec mov ebp,esp -d: 90 nop -e: 90 nop -f: 90 nop -10: 90 nop -11: 90 nop */ -unsigned char hookMe2[] = {0x55, 0x8b, 0xec, 0x74, 0xFB, 0x74, 0xea, 0x74, 0xFA, 0x8b, 0xec,0x8b, 0xec,0x8b, 0xec,0x90, 0x90, 0x90, 0x90, 0x90}; -uint64_t nullTramp = NULL; -NOINLINE void __cdecl h_nullstub() { - volatile int i = 0; - PH_UNUSED(i); +unsigned char hookMe2[] = { + 0x55, // [00] push ebp + 0x8b, 0xec, // [01] mov ebp,esp + 0x74, 0xfb, // [03] je 0x0 + 0x74, 0xfa, // [05] je 0x1 + 0x8b, 0xec, // [07] mov ebp,esp + 0x8b, 0xec, // [09] mov ebp,esp + 0x8b, 0xec, // [0B] mov ebp,esp + 0x90, // [0D] nop + 0x90, // [0E] nop + 0x90, // [0F] nop + 0x90, // [10] nop + 0x90, // [11] nop + 0x90, // [12] nop +}; + +uint64_t nullTramp = 0; +NOINLINE void h_nullstub() { + PLH::StackCanary canary; + PLH_STOP_OPTIMIZATIONS(); } -/* -0: 55 push ebp -1: 89 e5 mov ebp,esp -3: 89 e5 mov ebp,esp -5: 89 e5 mov ebp,esp -7: 89 e5 mov ebp,esp -9: 90 nop -a: 90 nop -b: 7f f4 jg 0x1 -*/ -unsigned char hookMe3[] = {0x55, 0x89, 0xE5, 0x89, 0xE5, 0x89, 0xE5, 0x89, 0xE5, 0x90, 0x90, 0x7F, 0xF4}; +unsigned char hookMe3[] = { + 0x55, // [00] push ebp + 0x89, 0xe5, // [01] mov ebp,esp + 0x89, 0xe5, // [03] mov ebp,esp + 0x89, 0xe5, // [05] mov ebp,esp + 0x89, 0xe5, // [07] mov ebp,esp + 0x90, // [09] nop + 0x90, // [0A] nop + 0x7f, 0xf4, // [0B] jg 0x1 + 0x90, // [0D] nop + 0x90, // [0E] nop + 0x90, // [0F] nop + 0x90, // [10] nop + 0x90, // [11] nop + 0x90, // [12] nop +}; + +uint8_t hookMe4[] = { + 0x55, // push ebp + 0x8b, 0xec, // mov ebp, esp + 0x56, // push esi + 0x8b, 0x75, 0x08, // mov esi, [ebp+8] + 0xf6, 0x46, 0x30, 0x02, // test byte ptr ds:[esi+0x30], 0x2 + 0x90, 0x90, 0x90, 0x90, // nop x4 + 0x90, 0x90, 0x90, 0x90, // nop x4 + 0xc3 // ret +}; + +// old NtQueueApcThread, call fs:0xC0 was weird +unsigned char hookMe5[] = { + 0xB8, 0X44, 0X00, 0X00, 0X00, // mov eax, 0x44 + 0x64, 0xff, 0x15, 0xc0, 0x00, 0x00, 0x00, // call dword ptr fs:0xc0 + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x7 + 0xc2, 0x14, 0x00 // retn 0x14 +}; NOINLINE void PH_ATTR_NAKED hookMeLoop() { -#ifdef _MSC_VER - __asm { - xor eax, eax - start : - inc eax - cmp eax, 5 - jle start - ret - } -#elif __GNUC__ - asm( - "xor %eax, %eax;\n\t" - "START: inc %eax;\n\t" - "cmp $5, %eax;\n\t" - "jle START;\n\t" - "ret;" - ); -#else -#error "Please implement this for your compiler!" -#endif + asm("xor %eax, %eax;\n" + "START: inc %eax;\n" + "cmp $5, %eax;\n" + "jle START;\n" + "ret;"); } -uint64_t hookMeLoopTramp = NULL; -HOOK_CALLBACK(&hookMeLoop, h_hookMeLoop, { - std::cout << "Hook loop Called!" << std::endl; - - effects.PeakEffect().trigger(); - PLH::FnCast(hookMeLoopTramp, &hookMeLoop)(); -}); +PLH_TEST_DETOUR_CALLBACK(hookMeLoop); -#include -uint64_t hookPrintfTramp = NULL; -NOINLINE int __cdecl h_hookPrintf(const char* format, ...) { +// PLH_TEST_DETOUR_CALLBACK doesn't support variadic functions yet. +uint64_t hookPrintfTramp = 0; +NOINLINE int h_hookPrintf(const char *format, ...) { char buffer[512]; va_list args; va_start(args, format); - vsprintf_s(buffer, format, args); + const auto written = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); + const std::string message = {buffer, static_cast(written)}; + effects.PeakEffect().trigger(); - return PLH::FnCast(hookPrintfTramp, &printf)("INTERCEPTED YO:%s", buffer); + return PLH::FnCast(hookPrintfTramp, &printf)("INTERCEPTED YO:%s", message.c_str()); } -#include // must specify specific overload of std::pow by assiging to pFn of type -double(*pFnPowDouble)(double, double) = &std::pow; - -uint64_t hookPowTramp = NULL; -HOOK_CALLBACK(pFnPowDouble, h_hookPow, { - effects.PeakEffect().trigger(); - return PLH::FnCast(hookPowTramp, pFnPowDouble)(_args...); -}); +const auto &pow_double = std::pow; +PLH_TEST_DETOUR_CALLBACK(pow_double); -#include -uint64_t hookMallocTramp = NULL; -HOOK_CALLBACK(&malloc, h_hookMalloc, { - effects.PeakEffect().trigger(); - return PLH::FnCast(hookMallocTramp, &malloc)(_args...); -}); +PLH_TEST_DETOUR_CALLBACK(malloc); -#include -uint64_t g_hook_recv_tramp = NULL; -void hkRecv(SOCKET s, char* buf, int len, int flags) -{ - PLH::FnCast(g_hook_recv_tramp, &hkRecv)(s, buf, len, flags); -} +#include +PLH_TEST_DETOUR_CALLBACK(recv); -TEMPLATE_TEST_CASE("Testing x86 detours", "[x86Detour],[ADetour]",PLH::ZydisDisassembler) { - TestType dis(PLH::Mode::x86); +TEST_CASE("Testing x86 detours", "[x86Detour][ADetour]") { + PLH::test::registerTestLogger(); SECTION("Normal function") { - PLH::x86Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + PLH::StackCanary canary; + PLH::x86Detour PLH_TEST_DETOUR(hookMe1); REQUIRE(detour.hook() == true); effects.PushEffect(); @@ -148,78 +139,94 @@ TEMPLATE_TEST_CASE("Testing x86 detours", "[x86Detour],[ADetour]",PLH::ZydisDisa } SECTION("Normal function rehook") { - PLH::x86Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + PLH::StackCanary canary; + PLH::x86Detour PLH_TEST_DETOUR(hookMe1); REQUIRE(detour.hook() == true); effects.PushEffect(); REQUIRE(detour.reHook() == true); // can only really test this doesn't cause memory corruption easily volatile auto result = hookMe1(); - PH_UNUSED(result); + REQUIRE(result == 2); REQUIRE(effects.PopEffect().didExecute()); REQUIRE(detour.unHook() == true); } SECTION("Jmp into prologue w/ src in range") { - PLH::x86Detour detour((char*)&hookMe2, (char*)&h_nullstub, &nullTramp, dis); + PLH::x86Detour detour((uint64_t)&hookMe2, (uint64_t)&h_nullstub, &nullTramp); REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); } SECTION("Jmp into prologue w/ src out of range") { - PLH::x86Detour detour((char*)&hookMe3, (char*)&h_nullstub, &nullTramp, dis); - //hookMe1Tramp = detour.getTrampoline(); + PLH::x86Detour detour((uint64_t)&hookMe3, (uint64_t)&h_nullstub, &nullTramp); REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); } - SECTION("Loop") { - PLH::x86Detour detour((char*)&hookMeLoop, (char*)h_hookMeLoop, &hookMeLoopTramp, dis); + SECTION("Test instruction in prologue") { + PLH::x86Detour detour((uint64_t)&hookMe4, (uint64_t)&h_nullstub, &nullTramp); REQUIRE(detour.hook() == true); + REQUIRE(detour.unHook() == true); + } - effects.PushEffect(); - hookMeLoop(); - REQUIRE(effects.PopEffect().didExecute()); + SECTION("Call with fs base") { + PLH::x86Detour detour((uint64_t)&hookMe5, (uint64_t)&h_nullstub, &nullTramp); + REQUIRE(detour.hook() == true); REQUIRE(detour.unHook() == true); } - SECTION("hook printf") { - PLH::x86Detour detour((char*)&printf, (char*)h_hookPrintf, &hookPrintfTramp, dis); + SECTION("Loop") { + PLH::x86Detour PLH_TEST_DETOUR(hookMeLoop); REQUIRE(detour.hook() == true); effects.PushEffect(); - printf("%s %f\n", "hi", .5f); - detour.unHook(); + hookMeLoop(); REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(detour.unHook() == true); } // it's a pun... + // ^ what pun? nothing found on the web >.< SECTION("hook pow") { - PLH::x86Detour detour((char*)pFnPowDouble, (char*)h_hookPow, &hookPowTramp, dis); + + PLH::x86Detour PLH_TEST_DETOUR(pow_double); REQUIRE(detour.hook() == true); effects.PushEffect(); - volatile double result = pFnPowDouble(2, 2); - PH_UNUSED(result); + volatile double result = pow_double(2, 2); + REQUIRE(result == 4.0); detour.unHook(); REQUIRE(effects.PopEffect().didExecute()); } SECTION("hook malloc") { - PLH::x86Detour detour((char*)&malloc, (char*)h_hookMalloc, &hookMallocTramp, dis); + PLH::x86Detour PLH_TEST_DETOUR(malloc); effects.PushEffect(); // catch does some allocations, push effect first so peak works REQUIRE(detour.hook() == true); - void* pMem = malloc(16); + void *pMem = malloc(16); free(pMem); detour.unHook(); // unhook so we can popeffect safely w/o catch allocation happening again REQUIRE(effects.PopEffect().didExecute()); } SECTION("hook recv") { - auto recv_addr = reinterpret_cast(GetProcAddress(GetModuleHandleA("ws2_32.dll"), "recv")); - PLH::x86Detour detour((char*)&malloc, (char*)h_hookMalloc, &recv_addr, dis); - effects.PushEffect(); // catch does some allocations, push effect first so peak works + PLH::x86Detour PLH_TEST_DETOUR(recv); REQUIRE(detour.hook() == true); } + + // TODO: Fix this. when making jmpToProl, relative displacement can only encode offset up to 0x7FFFFFFF + // But during tests, distance between prologue and trampoline was larger than that, leading to incorrect jump. + SECTION("hook printf") { + PLH::x86Detour detour((uint64_t)&printf, (uint64_t)h_hookPrintf, &hookPrintfTramp); + REQUIRE(detour.hook() == true); + + effects.PushEffect(); + printf("%s %f\n", "hi", .5f); + detour.unHook(); + REQUIRE(effects.PopEffect().didExecute()); + } } + +// NOLINTEND(*-err58-cpp) \ No newline at end of file diff --git a/UnitTests/linux/TestDisassembler.cpp b/UnitTests/linux/TestDisassembler.cpp index b5222da9..4b2cd09e 100644 --- a/UnitTests/linux/TestDisassembler.cpp +++ b/UnitTests/linux/TestDisassembler.cpp @@ -25,8 +25,8 @@ std::vector x64ASM = { 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA }; -std::vector x64ASM2 = { - 0x48, 0x8B, 0x05, 0x10, 0x00, 0x00, 0x00, // mov rax,QWORD PTR[rip + 0x10] +std::vector x64ASM2 = { + 0x48, 0x8B, 0x05, 0x10, 0x00, 0x00, 0x00, // mov rax,QWORD PTR[rip + 0x10] 0x48, 0x8B, 0x90, 0x55, 0x02, 0x00, 0x00 // mov rdx,QWORD PTR[rax + 0x255] }; @@ -62,10 +62,10 @@ std::vector x86x64Nops = { 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x66, 0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, /* - * + * x64/x86 capstone [1]: 90 nop [2]: 66 90 nop @@ -252,12 +252,20 @@ TEMPLATE_TEST_CASE("Test Disassemblers x86 FF25", "[ZydisDisassembler]", PLH::Zy #endif // re-write ff 25 displacement to point to data (absolute) - *(uint32_t*)(x86ASM_FF25.data() + 2) = (uint32_t)(x86ASM_FF25.data() + 6); // 0xFF25 = &mem; (just fyi *mem == 0xAA0000AB) + const auto jmp_address_ptr = x86ASM_FF25.data() + 2; + constexpr auto address_length = sizeof(size_t); + + // 0xFF25 = &mem; (just fyi *mem == 0xAA0000AB) + memcpy(jmp_address_ptr, jmp_address_ptr + address_length, address_length); PLH::StackCanary canaryg; TestType disasm(PLH::Mode::x86); - auto Instructions = disasm.disassemble((uint64_t)&x86ASM_FF25.front(), (uint64_t)&x86ASM_FF25.front(), - (uint64_t)&x86ASM_FF25.front() + x86ASM_FF25.size(), PLH::MemAccessor()); + auto Instructions = disasm.disassemble( + (uint64_t)x86ASM_FF25.data(), + (uint64_t)x86ASM_FF25.data(), + (uint64_t)(x86ASM_FF25.data() + address_length), + PLH::MemAccessor() + ); SECTION("Check disassembler integrity") { PLH::StackCanary canary; @@ -426,7 +434,7 @@ TEMPLATE_TEST_CASE("Test Disassemblers NOPS", "[ZydisDisassembler]", PLH::ZydisD SECTION("Verify multi-byte nops decodings x86") { for (auto& ins : Instructionsx86) { REQUIRE(ins.getMnemonic() == "nop"); - REQUIRE(TestType::isPadBytes(ins)); + REQUIRE(TestType::isPadBytes(ins)); } } } diff --git a/UnitTests/linux/TestMemProtector.cpp b/UnitTests/linux/TestMemProtector.cpp index 739ecfb9..ba46c48d 100644 --- a/UnitTests/linux/TestMemProtector.cpp +++ b/UnitTests/linux/TestMemProtector.cpp @@ -66,4 +66,6 @@ TEST_CASE("Test setting page protections", "[MemProtector]") { REQUIRE(prot2.originalProt() == (PLH::ProtFlag::X | PLH::ProtFlag::W)); } munmap(page, 4*1024); -} \ No newline at end of file +} + +// TODO: test cases when memory slice spans multiple mappings diff --git a/UnitTests/windows/TestDetourx86.cpp b/UnitTests/windows/TestDetourx86.cpp index 7c685604..51920014 100644 --- a/UnitTests/windows/TestDetourx86.cpp +++ b/UnitTests/windows/TestDetourx86.cpp @@ -14,7 +14,9 @@ is out of our control but typically returning volatile things, volatile locals, printf inside the body can mitigate this significantly. Do serious checking in debug or releasewithdebinfo mode (relwithdebinfo optimizes sliiiightly less)**/ -EffectTracker effects; +namespace { + EffectTracker effects; +} NOINLINE int __cdecl hookMe1() { volatile int var = 1; diff --git a/UnitTests/windows/TestDisassembler.cpp b/UnitTests/windows/TestDisassembler.cpp index 110b586b..2a2f88d7 100644 --- a/UnitTests/windows/TestDisassembler.cpp +++ b/UnitTests/windows/TestDisassembler.cpp @@ -252,12 +252,20 @@ TEST_CASE("Test Disassemblers x86 FF25", "[ZydisDisassembler]") { #endif // re-write ff 25 displacement to point to data (absolute) - *(uint32_t*)(x86ASM_FF25.data() + 2) = (uint32_t)(x86ASM_FF25.data() + 6); // 0xFF25 = &mem; (just fyi *mem == 0xAA0000AB) + const auto jmp_address_ptr = x86ASM_FF25.data() + 2; + constexpr auto address_length = sizeof(size_t); + + // 0xFF25 = &mem; (just fyi *mem == 0xAA0000AB) + memcpy(jmp_address_ptr, jmp_address_ptr + address_length, address_length); PLH::StackCanary canaryg; PLH::ZydisDisassembler disasm(PLH::Mode::x86); - auto Instructions = disasm.disassemble((uint64_t)&x86ASM_FF25.front(), (uint64_t)&x86ASM_FF25.front(), - (uint64_t)&x86ASM_FF25.front() + x86ASM_FF25.size(), PLH::MemAccessor()); + auto Instructions = disasm.disassemble( + (uint64_t)x86ASM_FF25.data(), + (uint64_t)x86ASM_FF25.data(), + (uint64_t)(jmp_address_ptr + address_length), + PLH::MemAccessor() + ); SECTION("Check disassembler integrity") { PLH::StackCanary canary; @@ -267,12 +275,16 @@ TEST_CASE("Test Disassemblers x86 FF25", "[ZydisDisassembler]") { std::cout << std::hex << "dest: " << p.first << " -> " << std::dec << p.second << std::endl; } + REQUIRE(Instructions.size() == 1); + // special little indirect ff25 jmp - REQUIRE(Instructions.back().getDestination() == 0xaa0000ab); + const auto& instruction = Instructions.at(0); + // TODO: Due to signed extension, instruction destination becomes 0xffffffffaa0000ab + // Is this intended or not? + REQUIRE(instruction.getDestination() == 0xaa0000ab); + REQUIRE(instruction.isBranching()); + REQUIRE(instruction.hasDisplacement()); } - - REQUIRE(Instructions.at(0).isBranching()); - REQUIRE(Instructions.at(0).hasDisplacement()); } TEST_CASE("Test Disassemblers x86", "[ZydisDisassembler]") { diff --git a/UnitTests/windows/TestEatHook.cpp b/UnitTests/windows/TestEatHook.cpp index 470b6710..b8c04fa2 100644 --- a/UnitTests/windows/TestEatHook.cpp +++ b/UnitTests/windows/TestEatHook.cpp @@ -62,6 +62,9 @@ int __stdcall hkEatMessageBox(HWND, LPCTSTR, LPCTSTR, UINT) { return 1; } +// Disable test in CI that require GUI interactions +#ifndef PLH_CI + TEST_CASE("Hook User32.MessageBoxA using module name", "[EatHook]") { PLH::StackCanary canary; LoadLibrary(TEXT("User32.dll")); @@ -83,6 +86,7 @@ TEST_CASE("Hook User32.MessageBoxA using module name", "[EatHook]") { hook.unHook(); } +#endif typedef DWORD(__stdcall* tGetTickCount)(); uint64_t oGetTickCount = 0; diff --git a/polyhook2/Detour/ADetour.hpp b/polyhook2/Detour/ADetour.hpp index 7519880b..83a06b77 100644 --- a/polyhook2/Detour/ADetour.hpp +++ b/polyhook2/Detour/ADetour.hpp @@ -101,7 +101,7 @@ class Detour : public PLH::IHook { const insts_t& func, uint64_t& minProlSz, uint64_t& roundProlSz - ); + ) const; /** * Insert nops from [Base, Base+size). diff --git a/polyhook2/Detour/x64Detour.hpp b/polyhook2/Detour/x64Detour.hpp index 5c9f5d9e..0a45eb68 100644 --- a/polyhook2/Detour/x64Detour.hpp +++ b/polyhook2/Detour/x64Detour.hpp @@ -65,9 +65,16 @@ class x64Detour : public Detour { optional generateTranslationRoutine(const Instruction& instruction, uint64_t resume_address); - bool make_inplace_trampoline(uint64_t base_address, const std::function& builder); + using builder_fn_t = std::function; + std::optional make_inplace_trampoline(uint64_t base_address, const builder_fn_t& builder); - bool allocate_jump_to_callback(); + bool allocate_jump_to_callback(const insts_t& originalInsts); + + bool fitHookInstsIntoPrologue( + const insts_t& originalInsts, + const insts_t& hookInsts, + detour_scheme_t chosenScheme + ); }; } diff --git a/polyhook2/IHook.hpp b/polyhook2/IHook.hpp index f921611b..d6595190 100644 --- a/polyhook2/IHook.hpp +++ b/polyhook2/IHook.hpp @@ -27,6 +27,7 @@ _Pragma("GCC optimize (\"O0\")") #define OPTS_ON __pragma(optimize("", on)) #endif +// TODO: Move this to test utils #define PH_UNUSED(a) (void)a namespace PLH { @@ -74,6 +75,8 @@ class IHook : public MemAccessor { bool m_hooked = false; }; +// TODO: Move to these to test utils + //Thanks @_can1357 for help with this. template struct callback_type { using type = T; }; @@ -124,7 +127,7 @@ struct callback_type \ #define __stdcall __attribute__((__stdcall__)) #endif -#ifndef POLYHOOK2_ARCH_X64 +#if defined(POLYHOOK2_ARCH_X86) && defined(POLYHOOK2_OS_WINDOWS) MAKE_CALLBACK_IMPL(__stdcall, __stdcall) MAKE_CALLBACK_CLASS_IMPL(__stdcall, __stdcall) diff --git a/polyhook2/Instruction.hpp b/polyhook2/Instruction.hpp index 0ea3b032..29bc05bf 100644 --- a/polyhook2/Instruction.hpp +++ b/polyhook2/Instruction.hpp @@ -74,6 +74,7 @@ class Instruction { m_accessor.safe_mem_read(dest, (uint64_t)&dest, sizeof(uint64_t), read); } else { // *(uint32_t*)dest; + dest &= 0x00000000FFFFFFFF; m_accessor.safe_mem_read(dest, (uint64_t)&dest, sizeof(uint32_t), read); } } @@ -445,7 +446,7 @@ inline PLH::insts_t makex64PreferredJump(const uint64_t address, const uint64_t * Destination should be the value that is written into destHolder, and be the address of where * the jmp should land.**/ inline PLH::insts_t makex64MinimumJump(const uint64_t address, const uint64_t destination, const uint64_t destHolder) { - PLH::Instruction::Displacement disp{ 0 }; + PLH::Instruction::Displacement disp{}; disp.Relative = PLH::Instruction::calculateRelativeDisplacement(address, destHolder, 6); std::vector destBytes; diff --git a/polyhook2/MemAccessor.hpp b/polyhook2/MemAccessor.hpp index d7779b33..ee8c9396 100644 --- a/polyhook2/MemAccessor.hpp +++ b/polyhook2/MemAccessor.hpp @@ -37,7 +37,7 @@ namespace PLH { reads from NO_ACCESS or otherwise innaccessible memory pages. Defaults to readprocessmemory. Must fail gracefully **/ - virtual bool safe_mem_read(uint64_t src, uint64_t dest, uint64_t size, size_t& read) const noexcept; + virtual bool safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t& read) const noexcept; virtual PLH::ProtFlag mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag newProtection, bool& status) const; }; diff --git a/polyhook2/PolyHookOs.hpp b/polyhook2/PolyHookOs.hpp index d531c3eb..42b12717 100644 --- a/polyhook2/PolyHookOs.hpp +++ b/polyhook2/PolyHookOs.hpp @@ -1,5 +1,4 @@ -#ifndef POLYHOOK_2_OS_HPP -#define POLYHOOK_2_OS_HPP +#pragma once #if defined(WIN64) || defined(_WIN64) || defined(__MINGW64__) #define POLYHOOK2_OS_WINDOWS @@ -89,4 +88,3 @@ #include void PolyHook2DebugBreak(); -#endif diff --git a/polyhook2/ZydisDisassembler.hpp b/polyhook2/ZydisDisassembler.hpp index e5e47266..63d99acf 100644 --- a/polyhook2/ZydisDisassembler.hpp +++ b/polyhook2/ZydisDisassembler.hpp @@ -63,7 +63,7 @@ class ZydisDisassembler { return (instruction.size() == 1 && bytes[0] == 0xCC) || (instruction.size() >= 2 && bytes[0] == 0xf3 && bytes[1] == 0xc3) || // rep ret (instruction.size() >= 2 && bytes[0] == 0xf2 && bytes[1] == 0xc3) || // bnd ret for Intel mpx - (mnemonic == "jmp" && !firstFunc) || // Jump to tranlslation + (mnemonic == "jmp" && !firstFunc) || // Jump to translation mnemonic == "ret" || mnemonic.find("iret") == 0; } diff --git a/sources/ADetour.cpp b/sources/ADetour.cpp index bea92231..479392b2 100644 --- a/sources/ADetour.cpp +++ b/sources/ADetour.cpp @@ -88,7 +88,7 @@ bool Detour::followJmp(insts_t& functionInsts, const uint8_t curDepth) { // NOLI return followJmp(functionInsts, curDepth + 1); // recurse } -bool Detour::expandProlSelfJmps(insts_t& prol, const insts_t& func, uint64_t& minProlSz, uint64_t& roundProlSz) { +bool Detour::expandProlSelfJmps(insts_t& prol, const insts_t& func, uint64_t& minProlSz, uint64_t& roundProlSz) const { uint64_t maxAddr = 0; const uint64_t prolStart = prol.front().getAddress(); const branch_map_t& branchMap = m_disasm.getBranchMap(); @@ -192,9 +192,14 @@ bool Detour::unHook() { m_trampoline = NULL; } - if (m_userTrampVar != nullptr) { - *m_userTrampVar = NULL; - } + // This code requires that m_userTrampVar is static or has global lifetime. + // But there is no way for us to enforce such a requirement, apart from documenting it somewhere. + // For example, if trampolineVariable is allocated on a stack, it will get corrupted after unhooking. + // Still, there is no real need for Polyhook to manage user's trampoline variable. + // It should be managed by the user instead. + // if (m_userTrampVar != nullptr) { + // *m_userTrampVar = NULL; + // } m_hooked = false; return true; diff --git a/sources/MemAccessor.cpp b/sources/MemAccessor.cpp index 7ed3bf9d..7a46b0c5 100644 --- a/sources/MemAccessor.cpp +++ b/sources/MemAccessor.cpp @@ -16,11 +16,11 @@ bool PLH::MemAccessor::safe_mem_write(uint64_t dest, uint64_t src, uint64_t size return WriteProcessMemory(GetCurrentProcess(), (char*)dest, (char*)src, (SIZE_T)size, (PSIZE_T)&written); } -bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, uint64_t size, size_t& read) const noexcept { +bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t& read) const noexcept { HANDLE process = GetCurrentProcess(); read = 0; - if (ReadProcessMemory(process, (char*)src, (char*)dest, (SIZE_T)size, (PSIZE_T)&read) && read > 0) + if (ReadProcessMemory(process, (char*)src, (char*)dest, size, (PSIZE_T)&read) && read > 0) return true; // Tries to read again on a partial copy, but limited by the end of the memory region @@ -69,10 +69,10 @@ static region_t get_region_from_addr(uint64_t addr) { ++strend; if (strend[0] == 'r') res.prot = res.prot | PLH::ProtFlag::R; - + if (strend[1] == 'w') res.prot = res.prot | PLH::ProtFlag::W; - + if (strend[2] == 'x') res.prot = res.prot | PLH::ProtFlag::X; @@ -83,6 +83,9 @@ static region_t get_region_from_addr(uint64_t addr) { } } } + + // TODO: What if we fail to find the region? + return res; } @@ -93,40 +96,77 @@ bool PLH::MemAccessor::mem_copy(uint64_t dest, uint64_t src, uint64_t size) cons bool PLH::MemAccessor::safe_mem_write(uint64_t dest, uint64_t src, uint64_t size, size_t& written) const noexcept { region_t region_infos = get_region_from_addr(src); - + // Make sure that the region we query is writable if(!(region_infos.prot & PLH::ProtFlag::W)) return false; - + size = std::min(region_infos.end - src, size); - + memcpy((void*)dest, (void*)src, (size_t)size); written = size; return true; } -bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, uint64_t size, size_t& read) const noexcept { - region_t region_infos = get_region_from_addr(src); - - // Make sure that the region we query is readable - if(!(region_infos.prot & PLH::ProtFlag::R)) +bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t &read) const noexcept { + if (!size) { return false; + } - size = std::min(region_infos.end - src, size); + auto address_in_region = src; + size_t readable_size = 0; + while (readable_size < size) { + const auto region = get_region_from_addr(address_in_region); + if (!(region.prot & PLH::ProtFlag::R)) { + // Stop when encountering an unreadable memory mapping. + break; + } - memcpy((void*)dest, (void*)src, (size_t)size); - read = size; + // Update the size of readable memory. + readable_size = region.end - src; + + // If the memory slice in question spans multiple memory maps, + // then we need to check the next adjacent memory map as well. + address_in_region = region.end; + } + + if (!readable_size) { + return false; + } + + read = std::min(size, readable_size); + memcpy((void *)dest, (void *)src, read); return true; } PLH::ProtFlag PLH::MemAccessor::mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag prot, bool& status) const { - region_t region_infos = get_region_from_addr(dest); - uint64_t aligned_dest = MEMORY_ROUND(dest, PLH::getPageSize()); - uint64_t aligned_size = MEMORY_ROUND_UP(size, PLH::getPageSize()); - status = mprotect((void*)aligned_dest, aligned_size, TranslateProtection(prot)) == 0; - return region_infos.prot; + auto current_address = dest; + size_t protected_size = 0; + region_t region{}; + while (current_address < dest + size) { + region = get_region_from_addr(current_address); + + const auto aligned_dest = region.start; + const auto aligned_size = region.end - region.start; + + status = mprotect((void *)aligned_dest, aligned_size, TranslateProtection(prot)) == 0; + + if (!status) { + break; + } + + protected_size += aligned_size; + + // If the memory slice in question spans multiple memory maps, + // then we need to protect the next adjacent memory map as well. + current_address = aligned_dest + aligned_size; + } + + // TODO: This is valid only for cases with single memory mapping. + // Ideally we should store an array of protections instead. + return region.prot; } #elif defined(POLYHOOK2_OS_APPLE) diff --git a/sources/PolyHookOs.cpp b/sources/PolyHookOs.cpp index 6f3cdb0c..a5f725be 100644 --- a/sources/PolyHookOs.cpp +++ b/sources/PolyHookOs.cpp @@ -1,24 +1,15 @@ #include "polyhook2/PolyHookOs.hpp" #include "polyhook2/PolyHookOsIncludes.hpp" -#if defined(POLYHOOK2_OS_WINDOWS) +#ifdef POLYHOOK2_OS_WINDOWS -void PolyHook2DebugBreak() -{ - __debugbreak(); +void PolyHook2DebugBreak() { + DebugBreak(); } -#elif defined(POLYHOOK2_OS_LINUX) +#else -void PolyHook2DebugBreak() -{ - __asm__("int3"); -} - -#elif defined(POLYHOOK2_OS_APPLE) - -void PolyHook2DebugBreak() -{ +void PolyHook2DebugBreak() { __asm__("int3"); } diff --git a/sources/ZydisDisassembler.cpp b/sources/ZydisDisassembler.cpp index 5ef117bd..234519d9 100644 --- a/sources/ZydisDisassembler.cpp +++ b/sources/ZydisDisassembler.cpp @@ -57,7 +57,7 @@ PLH::insts_t PLH::ZydisDisassembler::disassemble( ZydisDecodedInstruction insInfo; uint64_t offset = 0; bool endHit = false; - while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(m_decoder, (char*) (buf + offset), (ZyanUSize) (read - offset), &insInfo, decoded_operands))) { + while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(m_decoder, buf + offset, (ZyanUSize) (read - offset), &insInfo, decoded_operands))) { Instruction::Displacement displacement = {}; displacement.Absolute = 0; @@ -74,7 +74,7 @@ PLH::insts_t PLH::ZydisDisassembler::disassemble( 0, false, false, - (uint8_t*) ((unsigned char*) buf + offset), + buf + offset, insInfo.length, ZydisMnemonicGetString(insInfo.mnemonic), opstr, diff --git a/sources/x64Detour.cpp b/sources/x64Detour.cpp index 8a7d2c83..cb0e2bc0 100644 --- a/sources/x64Detour.cpp +++ b/sources/x64Detour.cpp @@ -13,6 +13,8 @@ #include "polyhook2/MemProtector.hpp" #include "polyhook2/Misc.hpp" +#include + namespace PLH { using std::optional; @@ -205,10 +207,7 @@ optional x64Detour::findNearestCodeCave(uint64_t address) { return {}; } -bool x64Detour::make_inplace_trampoline( - uint64_t base_address, - const std::function& builder -) { +std::optional x64Detour::make_inplace_trampoline(const uint64_t base_address, const builder_fn_t& builder) { CodeHolder code; code.init(m_asmjit_rt.environment(), base_address); x86::Assembler a(&code); @@ -216,50 +215,109 @@ bool x64Detour::make_inplace_trampoline( builder(a); uint64_t trampoline_address; - auto error = m_asmjit_rt.add(&trampoline_address, &code); - - if (error) { + if (const auto error = m_asmjit_rt.add(&trampoline_address, &code)) { const auto message = std::string("Failed to generate in-place trampoline: ") + asmjit::DebugUtils::errorAsString(error); PLH::Log::log(message, PLH::ErrorLevel::SEV); - return false; + return {}; } const auto trampoline_end = trampoline_address + code.codeSize(); - m_hookInsts = m_disasm.disassemble(trampoline_address, trampoline_address, trampoline_end, *this); + auto hookInsts = m_disasm.disassemble(trampoline_address, trampoline_address, trampoline_end, *this); // Fix the addresses auto current_address = base_address; - for (auto& inst: m_hookInsts) { + for (auto& inst: hookInsts) { inst.setAddress(current_address); current_address += inst.size(); } - return true; + return hookInsts; } -bool x64Detour::allocate_jump_to_callback() { + +bool x64Detour::fitHookInstsIntoPrologue( + const insts_t& originalInsts, + const insts_t& hookInsts, + const detour_scheme_t chosenScheme +) { + // min size of patches that may split instructions + // For valloc & code cave, we insert the jump, hence we take only size of the 1st instruction. + // For inplace, we calculate the size of the generated code. + uint64_t minProlSz = (chosenScheme == VALLOC2 || chosenScheme == CODE_CAVE) + ? hookInsts.begin()->size() + : hookInsts.rbegin()->getAddress() + hookInsts.rbegin()->size() - hookInsts.begin()->getAddress(); + + uint64_t roundProlSz = minProlSz; // nearest size to min that doesn't split any instructions + + // find the prologue section we will overwrite with jmp + zero or more nops + const auto prologueOpt = calcNearestSz(originalInsts, minProlSz, roundProlSz); + + if (!prologueOpt) { + Log::log("Function too small to hook safely!", ErrorLevel::WARN); + return false; + } + + assert(roundProlSz >= minProlSz); + auto prologue = *prologueOpt; + + if (!expandProlSelfJmps(prologue, originalInsts, minProlSz, roundProlSz)) { + Log::log("Function needs a prologue jmp table but it's too small to insert one", ErrorLevel::WARN); + return false; + } + + m_chosen_scheme = chosenScheme; + m_originalInsts = prologue; + m_hookInsts = hookInsts, + m_hookSize = (uint32_t) roundProlSz; + m_nopProlOffset = (uint16_t) minProlSz; + + Log::log("Prologue to overwrite:\n" + instsToStr(prologue) + "\n", ErrorLevel::INFO); + + // copy all the prologue stuff to trampoline + insts_t jmpTblOpt; + if (!makeTrampoline(prologue, jmpTblOpt)) { + return false; + } + + Log::log("m_trampoline: " + int_to_hex(m_trampoline) + "\n", ErrorLevel::INFO); + Log::log("m_trampolineSz: " + int_to_hex(m_trampolineSz) + "\n", ErrorLevel::INFO); + + auto tramp_instructions = m_disasm.disassemble(m_trampoline, m_trampoline, m_trampoline + m_trampolineSz, *this); + Log::log("Trampoline:\n" + instsToStr(tramp_instructions) + "\n", ErrorLevel::INFO); + if (!jmpTblOpt.empty()) { + Log::log("Trampoline Jmp Tbl:\n" + instsToStr(jmpTblOpt) + "\n", ErrorLevel::INFO); + } + + return true; +} + +bool x64Detour::allocate_jump_to_callback(const insts_t& originalInsts) { // Insert valloc description if (m_detourScheme & detour_scheme_t::VALLOC2 && boundedAllocSupported()) { - auto max = (uint64_t) AlignDownwards(calc_2gb_above(m_fnAddress), getPageSize()); - auto min = (uint64_t) AlignDownwards(calc_2gb_below(m_fnAddress), getPageSize()); + Log::log(std::format("Trying scheme: {}", printDetourScheme(detour_scheme_t::VALLOC2)), ErrorLevel::INFO); + + auto max = AlignDownwards(calc_2gb_above(m_fnAddress), getPageSize()); + auto min = AlignDownwards(calc_2gb_below(m_fnAddress), getPageSize()); // each block is m_blocksize (8) at the time of writing. Do not write more than this. auto region = (uint64_t) m_allocator.allocate(min, max); if (!region) { - Log::log("VirtualAlloc2 failed to find a region near function", ErrorLevel::SEV); + Log::log("VirtualAlloc2 failed to find a region near function", ErrorLevel::WARN); } else if (region < min || region >= max) { // Workaround for WINE bug, VirtualAlloc2 does not return region in the correct range (always?) // see: https://github.com/stevemk14ebr/PolyHook_2_0/pull/168 m_allocator.deallocate(region); region = 0; - Log::log("VirtualAlloc2 failed allocate within requested range", ErrorLevel::SEV); + Log::log("VirtualAlloc2 failed allocate within requested range", ErrorLevel::WARN); // intentionally try other schemes. } else { - m_valloc2_region = region; + const auto hookInsts = makex64MinimumJump(m_fnAddress, m_fnCallback, region); + + if (fitHookInstsIntoPrologue(originalInsts, hookInsts, detour_scheme_t::VALLOC2)) { + MemoryProtector region_protector(region, 8, ProtFlag::RWX, *this, false); + m_valloc2_region = region; - MemoryProtector region_protector(region, 8, ProtFlag::RWX, *this, false); - m_hookInsts = makex64MinimumJump(m_fnAddress, m_fnCallback, region); - m_chosen_scheme = detour_scheme_t::VALLOC2; - return true; + return true; + } } } @@ -267,7 +325,9 @@ bool x64Detour::allocate_jump_to_callback() { // otherwise this will overwrite adjacent bytes. The default in-place scheme is non-spoiling, // but larger, which reduces chances of success. if (m_detourScheme & detour_scheme_t::INPLACE) { - const auto success = make_inplace_trampoline(m_fnAddress, [&](auto& a) { + Log::log(std::format("Trying scheme: {}", printDetourScheme(detour_scheme_t::INPLACE)), ErrorLevel::INFO); + + const auto hookInsts = make_inplace_trampoline(m_fnAddress, [&](auto& a) { a.lea(x86::rsp, x86::ptr(x86::rsp, -0x80)); a.push(x86::rax); a.mov(x86::rax, m_fnCallback); @@ -275,21 +335,24 @@ bool x64Detour::allocate_jump_to_callback() { a.ret(0x80); }); - if (success) { - m_chosen_scheme = detour_scheme_t::INPLACE; - return true; + if (hookInsts && fitHookInstsIntoPrologue(originalInsts, *hookInsts, detour_scheme_t::INPLACE)) { + return true; } } - // Code cave is our last recommended approach since it may potentially find a region of unstable memory. + // Code cave is our almost last recommended approach since it may potentially find a region of unstable memory. // We're really space constrained, try to do some stupid hacks like checking for 0xCC's near us if (m_detourScheme & detour_scheme_t::CODE_CAVE) { - auto cave = findNearestCodeCave<8>(m_fnAddress); - if (cave) { - MemoryProtector cave_protector(*cave, 8, ProtFlag::RWX, *this, false); - m_hookInsts = makex64MinimumJump(m_fnAddress, m_fnCallback, *cave); - m_chosen_scheme = detour_scheme_t::CODE_CAVE; - return true; + Log::log(std::format("Trying scheme: {}", printDetourScheme(detour_scheme_t::CODE_CAVE)), ErrorLevel::INFO); + + if (const auto cave = findNearestCodeCave<8>(m_fnAddress)) { + Log::log(std::format("Found code cave at: {}", (void*) *cave), ErrorLevel::INFO); + const auto hookInsts = makex64MinimumJump(m_fnAddress, m_fnCallback, *cave); + if (fitHookInstsIntoPrologue(originalInsts, hookInsts, detour_scheme_t::CODE_CAVE)) { + MemoryProtector cave_protector(*cave, 8, ProtFlag::RWX, *this, false); + + return true; + } } Log::log("No code caves found near function", ErrorLevel::SEV); @@ -298,25 +361,27 @@ bool x64Detour::allocate_jump_to_callback() { // This short in-place scheme works almost like the default in-place scheme, except that it doesn't // try to not spoil shadow space. It doesn't mean that it will necessarily spoil it, though. if (m_detourScheme & detour_scheme_t::INPLACE_SHORT) { - const auto success = make_inplace_trampoline(m_fnAddress, [&](auto& a) { + Log::log(std::format("Trying scheme: {}", printDetourScheme(detour_scheme_t::INPLACE_SHORT)), ErrorLevel::INFO); + + const auto hookInsts = make_inplace_trampoline(m_fnAddress, [&](auto& a) { a.mov(x86::rax, m_fnCallback); a.push(x86::rax); a.ret(); }); - if (success) { - m_chosen_scheme = detour_scheme_t::INPLACE_SHORT; - return true; + if (hookInsts && fitHookInstsIntoPrologue(originalInsts, *hookInsts, detour_scheme_t::INPLACE_SHORT)) { + return true; } } Log::log("None of the allowed hooking schemes have succeeded", ErrorLevel::SEV); - if (m_hookInsts.empty()) { - Log::log("Invalid state: hook instructions are empty", ErrorLevel::SEV); + if (!m_hookInsts.empty()) { + // Since we failed, we expect hook instructions to be empty + Log::log("Invalid state: hook instructions are not empty", ErrorLevel::SEV); } - return false; + return {}; } bool x64Detour::hook() { @@ -338,64 +403,18 @@ bool x64Detour::hook() { // update given fn address to resolved one m_fnAddress = insts.front().getAddress(); - if (!allocate_jump_to_callback()) { + if (!allocate_jump_to_callback(insts)) { return false; } - { - std::stringstream ss; - ss << printDetourScheme(m_chosen_scheme); - Log::log("Chosen detour scheme: " + ss.str() + "\n", ErrorLevel::INFO); - } - - // min size of patches that may split instructions - // For valloc & code cave, we insert the jump, hence we take only size of the 1st instruction. - // For detours, we calculate the size of the generated code. - uint64_t minProlSz = (m_chosen_scheme == VALLOC2 || m_chosen_scheme == CODE_CAVE) ? m_hookInsts.begin()->size() : - m_hookInsts.rbegin()->getAddress() + m_hookInsts.rbegin()->size() - - m_hookInsts.begin()->getAddress(); - - uint64_t roundProlSz = minProlSz; // nearest size to min that doesn't split any instructions - - // find the prologue section we will overwrite with jmp + zero or more nops - auto prologueOpt = calcNearestSz(insts, minProlSz, roundProlSz); - if (!prologueOpt) { - Log::log("Function too small to hook safely!", ErrorLevel::SEV); - return false; - } - - assert(roundProlSz >= minProlSz); - auto prologue = *prologueOpt; - - if (!expandProlSelfJmps(prologue, insts, minProlSz, roundProlSz)) { - Log::log("Function needs a prologue jmp table but it's too small to insert one", ErrorLevel::SEV); - return false; - } - - m_originalInsts = prologue; - - Log::log("Prologue to overwrite:\n" + instsToStr(prologue) + "\n", ErrorLevel::INFO); - - // copy all the prologue stuff to trampoline - insts_t jmpTblOpt; - if (!makeTrampoline(prologue, jmpTblOpt)) { - return false; - } - Log::log("m_trampoline: " + int_to_hex(m_trampoline) + "\n", ErrorLevel::INFO); - Log::log("m_trampolineSz: " + int_to_hex(m_trampolineSz) + "\n", ErrorLevel::INFO); - - auto tramp_instructions = m_disasm.disassemble(m_trampoline, m_trampoline, m_trampoline + m_trampolineSz, *this); - Log::log("Trampoline:\n" + instsToStr(tramp_instructions) + "\n", ErrorLevel::INFO); - if (!jmpTblOpt.empty()) { - Log::log("Trampoline Jmp Tbl:\n" + instsToStr(jmpTblOpt) + "\n", ErrorLevel::INFO); - } + Log::log(std::format("Chosen detour scheme: {}", printDetourScheme(m_chosen_scheme)) , ErrorLevel::INFO); *m_userTrampVar = m_trampoline; - m_hookSize = (uint32_t) roundProlSz; - m_nopProlOffset = (uint16_t) minProlSz; Log::log("Hook instructions: \n" + instsToStr(m_hookInsts) + "\n", ErrorLevel::INFO); - MemoryProtector prot(m_fnAddress, m_hookSize, ProtFlag::RWX, *this); + // We must not unset protections on destroy, since this will remove the Write permission + // that might be used by other instructions allocated within the same memory mapping + MemoryProtector prot(m_fnAddress, m_hookSize, ProtFlag::RWX, *this, false); ZydisDisassembler::writeEncoding(m_hookInsts, *this); Log::log("Hook size: " + std::to_string(m_hookSize) + "\n", ErrorLevel::INFO); @@ -426,49 +445,65 @@ bool x64Detour::unHook() { * we also need to store it: `add rax, rbx` && `mov [r15], rax`, where as in cmp instruction for * instance there is no such requirement. */ -const static std::set instructions_to_store{ // NOLINT(cert-err58-cpp) - "adc", "add", "and", "bsf", "bsr", "btc", "btr", "bts", - "cmovb", "cmove", "cmovl", "cmovle", "cmovnb", "cmovnbe", "cmovnl", "cmovnle", - "cmovno", "cmovnp", "cmovns", "cmovnz", "cmovo", "cmovp", "cmovs", "cmovz", - "cmpxchg", "crc32", "cvtsi2sd", "cvtsi2ss", "dec", "extractps", "inc", "mov", - "neg", "not", "or", "pextrb", "pextrd", "pextrq", "rcl", "rcr", "rol", "ror", - "sal", "sar", "sbb", "setb", "setbe", "setl", "setle", "setnb", "setnbe", "setnl", - "setnle", "setno", "setnp", "setns", "setnz", "seto", "setp", "sets", "setz", "shl", - "shld", "shr", "shrd", "sub", "verr", "verw", "xadd", "xchg", "xor" -}; +const auto& get_instructions_to_store() { + const static std::set instructions_to_store = { + "adc", "add", "and", "bsf", "bsr", "btc", "btr", "bts", + "cmovb", "cmove", "cmovl", "cmovle", "cmovnb", "cmovnbe", "cmovnl", "cmovnle", + "cmovno", "cmovnp", "cmovns", "cmovnz", "cmovo", "cmovp", "cmovs", "cmovz", + "cmpxchg", "crc32", "cvtsi2sd", "cvtsi2ss", "dec", "extractps", "inc", "mov", + "neg", "not", "or", "pextrb", "pextrd", "pextrq", "rcl", "rcr", "rol", "ror", + "sal", "sar", "sbb", "setb", "setbe", "setl", "setle", "setnb", "setnbe", "setnl", + "setnle", "setno", "setnp", "setns", "setnz", "seto", "setp", "sets", "setz", "shl", + "shld", "shr", "shrd", "sub", "verr", "verw", "xadd", "xchg", "xor" + }; + + return instructions_to_store; +} -const static std::map a_to_b{ // NOLINT(cert-err58-cpp) - {ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_RBX}, - {ZYDIS_REGISTER_EAX, ZYDIS_REGISTER_EBX}, - {ZYDIS_REGISTER_AX, ZYDIS_REGISTER_BX}, - {ZYDIS_REGISTER_AH, ZYDIS_REGISTER_BH}, - {ZYDIS_REGISTER_AL, ZYDIS_REGISTER_BL}, -}; +const auto& get_a_to_b() { + const static std::map a_to_b{ + {ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_RBX}, + {ZYDIS_REGISTER_EAX, ZYDIS_REGISTER_EBX}, + {ZYDIS_REGISTER_AX, ZYDIS_REGISTER_BX}, + {ZYDIS_REGISTER_AH, ZYDIS_REGISTER_BH}, + {ZYDIS_REGISTER_AL, ZYDIS_REGISTER_BL}, + }; -const static std::map class_to_reg{ // NOLINT(cert-err58-cpp) - {ZYDIS_REGCLASS_GPR64, ZYDIS_REGISTER_RAX}, - {ZYDIS_REGCLASS_GPR32, ZYDIS_REGISTER_EAX}, - {ZYDIS_REGCLASS_GPR16, ZYDIS_REGISTER_AX}, - {ZYDIS_REGCLASS_GPR8, ZYDIS_REGISTER_AL}, -}; + return a_to_b; +} + +const auto& get_class_to_reg() { + const static std::map class_to_reg{ + {ZYDIS_REGCLASS_GPR64, ZYDIS_REGISTER_RAX}, + {ZYDIS_REGCLASS_GPR32, ZYDIS_REGISTER_EAX}, + {ZYDIS_REGCLASS_GPR16, ZYDIS_REGISTER_AX}, + {ZYDIS_REGCLASS_GPR8, ZYDIS_REGISTER_AL}, + }; + + return class_to_reg; +} /** * For push/pop operations, we have to use 64-bit operands. * This map translates all possible scratch registers into * the corresponding 64-bit register for push/pop operations. */ -const static std::map scratch_to_64{ // NOLINT(cert-err58-cpp) - {"rbx", "rbx"}, - {"ebx", "rbx"}, - {"bx", "rbx"}, - {"bh", "rbx"}, - {"bl", "rbx"}, - {"rax", "rax"}, - {"eax", "rax"}, - {"ax", "rax"}, - {"ah", "rax"}, - {"al", "rax"}, -}; +const auto& get_scratch_to_64() { + const static std::map scratch_to_64{ + {"rbx", "rbx"}, + {"ebx", "rbx"}, + {"bx", "rbx"}, + {"bh", "rbx"}, + {"bl", "rbx"}, + {"rax", "rax"}, + {"eax", "rax"}, + {"ax", "rax"}, + {"ah", "rax"}, + {"al", "rax"}, + }; + + return scratch_to_64; +} struct TranslationResult { string instruction; @@ -522,12 +557,12 @@ optional translate_instruction(const Instruction& instruction const auto regClass = ZydisRegisterGetClass(reg); const string reg_string = ZydisRegisterGetString(reg); - if (a_to_b.count(reg)) { + if (get_a_to_b().contains(reg)) { // This is a register A - scratch_register = a_to_b.at(reg); - } else if (class_to_reg.count(regClass)) { + scratch_register = get_a_to_b().at(reg); + } else if (get_class_to_reg().contains(regClass)) { // This is not a register A - scratch_register = class_to_reg.at(regClass); + scratch_register = get_class_to_reg().at(regClass); } else { // Unexpected register Log::log("Unexpected register: " + reg_string, ErrorLevel::SEV); @@ -536,7 +571,7 @@ optional translate_instruction(const Instruction& instruction scratch_register_string = ZydisRegisterGetString(scratch_register); - if (!scratch_to_64.count(scratch_register_string)) { + if (!get_scratch_to_64().contains(scratch_register_string)) { Log::log("Unexpected scratch register: " + scratch_register_string, ErrorLevel::SEV); return {}; } @@ -602,7 +637,7 @@ optional x64Detour::generateTranslationRoutine(const Instruction& inst if (instruction.getMnemonic() == "lea") { // lea rax, ds:[0x00007FFD4FFDC400] uint64_t relativeDest = instruction.getRelativeDestination(); const string reg_string = ZydisRegisterGetString(instruction.getRegister()); - + // translate the relative LEA into a fixed MOV, using same register and it's computed relative address translation.emplace_back("mov " + reg_string + ", " + int_to_hex(relativeDest)); } else { @@ -613,7 +648,7 @@ optional x64Detour::generateTranslationRoutine(const Instruction& inst auto [translated_instruction, scratch_register, address_register] = *result; - const auto& scratch_register_64 = scratch_to_64.at(scratch_register); + const auto& scratch_register_64 = get_scratch_to_64().at(scratch_register); // Save the scratch register translation.emplace_back("push " + scratch_register_64); @@ -632,7 +667,7 @@ optional x64Detour::generateTranslationRoutine(const Instruction& inst translation.emplace_back(translated_instruction); // Store the scratch register content into the destination, if necessary - if (instruction.startsWithDisplacement() && instructions_to_store.count(instruction.getMnemonic())) { + if (instruction.startsWithDisplacement() && get_instructions_to_store().contains(instruction.getMnemonic())) { translation.emplace_back("mov [" + address_register + "], " + scratch_register_64); } @@ -780,7 +815,7 @@ bool x64Detour::makeTrampoline(insts_t& prologue, insts_t& outJmpTable) { const auto trampoline_end = m_trampoline + m_trampolineSz; // & ~0x7 for 8 bytes align for performance. - const uint64_t jmpHolderCurAddr = (trampoline_end - destHldrSz) & ~0x7; + const uint64_t jmpHolderCurAddr = (trampoline_end - destHldrSz) & ~alignment_pad_size; const auto jmpToProl = makex64MinimumJump(jmpToProlAddr, prolStart + prolSz, jmpHolderCurAddr); Log::log("Jmp To Prol:\n" + instsToStr(jmpToProl) + "\n", ErrorLevel::INFO);