diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7..f0f41f0 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e572dbb..d0afa78 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -7,6 +7,7 @@ add_executable(mach_detours_tests
test_threads.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.
# Between attach and commit the target function's code page is not executable, which can mean our test driver code would not
@@ -15,10 +16,11 @@ add_library(mach_detours_tests_lib SHARED
lib_function.c
lib_function.h
)
+target_compile_options(mach_detours_tests_lib PRIVATE "-O0")
target_link_libraries(mach_detours_tests
PRIVATE
mach_detours
Catch2::Catch2WithMain
mach_detours_tests_lib
-)
\ No newline at end of file
+)
diff --git a/tests/test_local_function.cpp b/tests/test_local_function.cpp
index 49b2d60..2f29e06 100644
--- a/tests/test_local_function.cpp
+++ b/tests/test_local_function.cpp
@@ -4,6 +4,9 @@
#include
+// 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;
int localFunction()
{
diff --git a/tests/test_threads.cpp b/tests/test_threads.cpp
index 11af631..f6d109f 100644
--- a/tests/test_threads.cpp
+++ b/tests/test_threads.cpp
@@ -1,5 +1,6 @@
// Copyright (c) Lysann Tranvouez. All rights reserved.
+#include
#include
#include
@@ -29,33 +30,62 @@ static const char* localFunctionDetour()
}
static const char* (*realLocalFunction)() = localFunction;
+struct threadData
+{
+ std::atomic loopCounter;
+ bool cancel = false;
+};
+
static void* threadFunc(void* pUserArg)
{
- const bool* pCancel = static_cast(pUserArg);
- while (!*pCancel) {
+ auto& data = *static_cast(pUserArg);
+ while (!data.cancel) {
localFunction();
localFunction();
localFunction();
localFunction();
localFunction();
+ ++data.loopCounter;
}
return nullptr;
}
+template
+bool resetLoopCountersAndWaitForLoop(std::array& threadDatas, std::optional 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]" )
{
- bool cancelThreads = false;
+ using namespace std::chrono_literals;
constexpr auto numThreads = 150;
pthread_t threads[numThreads];
+ std::array threadDatas{};
for (auto i = 0; i < numThreads; i++) {
- pthread_create(&threads[i], nullptr, threadFunc, (void*)&cancelThreads);
+ pthread_create(&threads[i], nullptr, threadFunc, (void*)&threadDatas[i]);
}
SECTION( "running threads modify a value" )
{
const int saved_b = g_b;
- usleep( 1000 ); // let the threads update g_b a bit
+ resetLoopCountersAndWaitForLoop(threadDatas);
CHECK( saved_b != g_b );
}
@@ -79,13 +109,13 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
}
int saved_b = g_b;
- usleep( 1000 ); // let any running thread update g_b a bit
+ resetLoopCountersAndWaitForLoop(threadDatas, 1s);
CHECK( saved_b == g_b );
CHECK( detour_transaction_commit() == err_none );
saved_b = g_b;
- usleep( 1000 ); // let the threads update g_b a bit
+ resetLoopCountersAndWaitForLoop(threadDatas);
CHECK( saved_b != g_b );
}
@@ -95,7 +125,7 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
CHECK( detour_transaction_abort() == err_none );
const int saved_b = g_b;
- usleep( 1000 ); // let the threads update g_b a bit
+ resetLoopCountersAndWaitForLoop(threadDatas);
CHECK( saved_b != g_b );
}
@@ -108,15 +138,17 @@ TEST_CASE( "Handling other threads", "[attach][local][threads]" )
CHECK( detour_attach_and_commit(reinterpret_cast(&realLocalFunction), reinterpret_cast(localFunctionDetour)) == err_none );
const int saved_b = g_b;
- usleep( 1000 ); // let the threads update g_b a bit
- CHECK( saved_b != g_b );
+ resetLoopCountersAndWaitForLoop(threadDatas);
+ CHECK( saved_b == g_b );
// clean up
CHECK( detour_transaction_begin_managed() == err_none );
CHECK( detour_detach_and_commit(reinterpret_cast(&realLocalFunction), reinterpret_cast(localFunctionDetour)) == err_none );
}
- cancelThreads = true;
+ for (auto i = 0; i < numThreads; i++) {
+ threadDatas[i].cancel = true;
+ }
for (auto i = 0; i < numThreads; i++) {
pthread_join(threads[i], nullptr);
}