Compare commits

..

2 commits

Author SHA1 Message Date
b62d10462c test
All checks were successful
/ test (push) Successful in 19s
2025-10-06 00:03:06 +02:00
e5f77fe98f test workflows
All checks were successful
/ test (push) Successful in 17s
2025-10-05 23:59:48 +02:00
5 changed files with 20 additions and 50 deletions

View file

@ -0,0 +1,8 @@
on: [push]
jobs:
test:
runs-on: docker
steps:
- run: echo All good!
- uses: actions/checkout@v4
- run: ls -la

1
.idea/vcs.xml generated
View file

@ -2,6 +2,5 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/lib/catch2" vcs="Git" />
</component> </component>
</project> </project>

View file

@ -7,7 +7,6 @@ add_executable(mach_detours_tests
test_threads.cpp test_threads.cpp
test_transaction.cpp test_transaction.cpp
) )
target_compile_options(mach_detours_tests PRIVATE "-O0")
# The target function must be in a shared library because otherwise it might be in the same code page as the test.cpp functions. # The target function must be in a shared library because otherwise it might be in the same code page as the test.cpp functions.
# Between attach and commit the target function's code page is not executable, which can mean our test driver code would not # Between attach and commit the target function's code page is not executable, which can mean our test driver code would not
@ -16,11 +15,10 @@ add_library(mach_detours_tests_lib SHARED
lib_function.c lib_function.c
lib_function.h lib_function.h
) )
target_compile_options(mach_detours_tests_lib PRIVATE "-O0")
target_link_libraries(mach_detours_tests target_link_libraries(mach_detours_tests
PRIVATE PRIVATE
mach_detours mach_detours
Catch2::Catch2WithMain Catch2::Catch2WithMain
mach_detours_tests_lib mach_detours_tests_lib
) )

View file

@ -4,9 +4,6 @@
#include <mach_detours.h> #include <mach_detours.h>
// Note that this file must be compiled without optimizations (or at least without inlining)
// else localFunction() calls below are likely going to be inlined at the call site, and detours will not work.
static int localFunctionCounter = 0; static int localFunctionCounter = 0;
int localFunction() int localFunction()
{ {

View file

@ -1,6 +1,5 @@
// Copyright (c) Lysann Tranvouez. All rights reserved. // Copyright (c) Lysann Tranvouez. All rights reserved.
#include <algorithm>
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <mach_detours.h> #include <mach_detours.h>
@ -30,62 +29,33 @@ static const char* localFunctionDetour()
} }
static const char* (*realLocalFunction)() = localFunction; static const char* (*realLocalFunction)() = localFunction;
struct threadData
{
std::atomic<int> loopCounter;
bool cancel = false;
};
static void* threadFunc(void* pUserArg) static void* threadFunc(void* pUserArg)
{ {
auto& data = *static_cast<threadData*>(pUserArg); const bool* pCancel = static_cast<const bool*>(pUserArg);
while (!data.cancel) { while (!*pCancel) {
localFunction(); localFunction();
localFunction(); localFunction();
localFunction(); localFunction();
localFunction(); localFunction();
localFunction(); localFunction();
++data.loopCounter;
} }
return nullptr; return nullptr;
} }
template<size_t numThreads>
bool resetLoopCountersAndWaitForLoop(std::array<threadData, numThreads>& threadDatas, std::optional<std::chrono::seconds> timeout = std::nullopt)
{
for (auto i = 0; i < numThreads; i++) {
threadDatas[i].loopCounter = 0;
}
const auto endTime = timeout ? std::make_optional(std::chrono::steady_clock::now() + *timeout) : std::nullopt;
while (true) {
// we check for >= 2 to be sure we did a full loop - we don't want to execute just the line incrementing the counter!
const bool anyLooped = std::ranges::any_of(threadDatas, [](const threadData& data){ return data.loopCounter >= 2; });
if (anyLooped) {
return true;
}
if (endTime && std::chrono::steady_clock::now() > *endTime) {
return false;
}
usleep( 100 );
}
}
TEST_CASE( "Handling other threads", "[attach][local][threads]" ) TEST_CASE( "Handling other threads", "[attach][local][threads]" )
{ {
using namespace std::chrono_literals; bool cancelThreads = false;
constexpr auto numThreads = 150; constexpr auto numThreads = 150;
pthread_t threads[numThreads]; pthread_t threads[numThreads];
std::array<threadData, numThreads> threadDatas{};
for (auto i = 0; i < numThreads; i++) { for (auto i = 0; i < numThreads; i++) {
pthread_create(&threads[i], nullptr, threadFunc, (void*)&threadDatas[i]); pthread_create(&threads[i], nullptr, threadFunc, (void*)&cancelThreads);
} }
SECTION( "running threads modify a value" ) SECTION( "running threads modify a value" )
{ {
const int saved_b = g_b; const int saved_b = g_b;
resetLoopCountersAndWaitForLoop(threadDatas); usleep( 1000 ); // let the threads update g_b a bit
CHECK( saved_b != g_b ); CHECK( saved_b != g_b );
} }
@ -109,13 +79,13 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
} }
int saved_b = g_b; int saved_b = g_b;
resetLoopCountersAndWaitForLoop(threadDatas, 1s); usleep( 1000 ); // let any running thread update g_b a bit
CHECK( saved_b == g_b ); CHECK( saved_b == g_b );
CHECK( detour_transaction_commit() == err_none ); CHECK( detour_transaction_commit() == err_none );
saved_b = g_b; saved_b = g_b;
resetLoopCountersAndWaitForLoop(threadDatas); usleep( 1000 ); // let the threads update g_b a bit
CHECK( saved_b != g_b ); CHECK( saved_b != g_b );
} }
@ -125,7 +95,7 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
CHECK( detour_transaction_abort() == err_none ); CHECK( detour_transaction_abort() == err_none );
const int saved_b = g_b; const int saved_b = g_b;
resetLoopCountersAndWaitForLoop(threadDatas); usleep( 1000 ); // let the threads update g_b a bit
CHECK( saved_b != g_b ); CHECK( saved_b != g_b );
} }
@ -138,17 +108,15 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
CHECK( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none ); CHECK( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none );
const int saved_b = g_b; const int saved_b = g_b;
resetLoopCountersAndWaitForLoop(threadDatas); usleep( 1000 ); // let the threads update g_b a bit
CHECK( saved_b == g_b ); CHECK( saved_b != g_b );
// clean up // clean up
CHECK( detour_transaction_begin_managed() == err_none ); CHECK( detour_transaction_begin_managed() == err_none );
CHECK( detour_detach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none ); CHECK( detour_detach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none );
} }
for (auto i = 0; i < numThreads; i++) { cancelThreads = true;
threadDatas[i].cancel = true;
}
for (auto i = 0; i < numThreads; i++) { for (auto i = 0; i < numThreads; i++) {
pthread_join(threads[i], nullptr); pthread_join(threads[i], nullptr);
} }