src/platform.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_PLATFORM_HPP | ||
| 2 | #define DETOURMODKIT_PLATFORM_HPP | ||
| 3 | |||
| 4 | #include <windows.h> | ||
| 5 | |||
| 6 | namespace DetourModKit::detail | ||
| 7 | { | ||
| 8 | /** | ||
| 9 | * @brief Checks if the current thread holds the Windows loader lock. | ||
| 10 | * @details Reads the loader-lock CRITICAL_SECTION pointer from the PEB at a well-known offset (0x110 on x64, 0xA0 | ||
| 11 | * on x86) that has been stable from Windows XP through 11, then compares its OwningThread to the current | ||
| 12 | * thread id. Thread joins are unsafe while the loader lock is held (DllMain context), so this gate decides | ||
| 13 | * detach-vs-join across every subsystem teardown. | ||
| 14 | * @note The result is fail-safe. If the PEB or the loader-lock pointer cannot be read, or the pointer does not | ||
| 15 | * resolve to committed, readable memory (e.g. a future or foreign PEB layout shifted the offset), the | ||
| 16 | * function assumes the lock IS held and returns true. A spurious true only leaks a detached thread (bounded, | ||
| 17 | * with the module pinned); a spurious false would join a thread under the loader lock and deadlock the host | ||
| 18 | * on unload, so uncertainty must bias toward true. | ||
| 19 | * @return true if the current thread holds the loader lock, or if ownership cannot be determined. | ||
| 20 | */ | ||
| 21 | 1075 | [[nodiscard]] inline bool is_loader_lock_held() noexcept | |
| 22 | { | ||
| 23 | #ifdef _WIN64 | ||
| 24 | 1075 | auto *peb = reinterpret_cast<char *>(__readgsqword(0x60)); | |
| 25 | 1075 | constexpr size_t LOADER_LOCK_OFFSET = 0x110; | |
| 26 | #else | ||
| 27 | auto *peb = reinterpret_cast<char *>(__readfsdword(0x30)); | ||
| 28 | constexpr size_t LOADER_LOCK_OFFSET = 0xA0; | ||
| 29 | #endif | ||
| 30 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1075 times.
|
1075 | if (!peb) |
| 31 | ✗ | return true; | |
| 32 | |||
| 33 | 1075 | auto *cs = *reinterpret_cast<PCRITICAL_SECTION *>(peb + LOADER_LOCK_OFFSET); | |
| 34 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1075 times.
|
1075 | if (!cs) |
| 35 | ✗ | return true; | |
| 36 | |||
| 37 | // Confirm the critical section lives in committed, readable memory before dereferencing OwningThread. A wrong | ||
| 38 | // LOADER_LOCK_OFFSET (foreign or future | ||
| 39 | // PEB layout) would otherwise read a bogus pointer and fault the host. | ||
| 40 | 1075 | constexpr DWORD READABLE_PROTECT = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | | |
| 41 | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; | ||
| 42 | 1075 | MEMORY_BASIC_INFORMATION mbi{}; | |
| 43 |
1/2✓ Branch 10 → 11 taken 1075 times.
✗ Branch 10 → 13 not taken.
|
2150 | if (VirtualQuery(cs, &mbi, sizeof(mbi)) != sizeof(mbi) || mbi.State != MEM_COMMIT || |
| 44 |
4/8✓ Branch 9 → 10 taken 1075 times.
✗ Branch 9 → 13 not taken.
✓ Branch 11 → 12 taken 1075 times.
✗ Branch 11 → 13 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 1075 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 1075 times.
|
2150 | (mbi.Protect & READABLE_PROTECT) == 0 || (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) != 0) |
| 45 | { | ||
| 46 | ✗ | return true; | |
| 47 | } | ||
| 48 | |||
| 49 | 1075 | return cs->OwningThread == reinterpret_cast<HANDLE>(static_cast<uintptr_t>(GetCurrentThreadId())); | |
| 50 | } | ||
| 51 | |||
| 52 | /** | ||
| 53 | * @brief Pins the current module to prevent unload while detached threads are still running. | ||
| 54 | * @details Uses GET_MODULE_HANDLE_EX_FLAG_PIN to permanently increment the module reference count. Safe to call | ||
| 55 | * from DllMain. This ensures that code pages and static data remain valid for detached threads during the | ||
| 56 | * FreeLibrary path. | ||
| 57 | * @return true if the module was successfully pinned, false on failure. | ||
| 58 | */ | ||
| 59 | 8 | inline bool pin_current_module() noexcept | |
| 60 | { | ||
| 61 | 8 | HMODULE pin = nullptr; | |
| 62 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 8 times.
|
8 | if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, |
| 63 | reinterpret_cast<LPCWSTR>(&pin_current_module), &pin)) | ||
| 64 | { | ||
| 65 | ✗ | OutputDebugStringA("DetourModKit: pin_current_module failed; " | |
| 66 | "detached threads may access unmapped code.\n"); | ||
| 67 | ✗ | return false; | |
| 68 | } | ||
| 69 | 8 | return true; | |
| 70 | } | ||
| 71 | |||
| 72 | } // namespace DetourModKit::detail | ||
| 73 | |||
| 74 | #endif // DETOURMODKIT_PLATFORM_HPP | ||
| 75 |