src/internal/hook_coordinator.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file internal/hook_coordinator.hpp | ||
| 6 | * @brief Backend-free hold on the process route coordinator for translation units outside the hook island. | ||
| 7 | * @details The backend serializes every patch transaction, including its trap window, through one process | ||
| 8 | * coordinator. A read of a code page outside that coordinator can record the transient trap protection | ||
| 9 | * (`[B-18]`, `[B-66]`, `[B-109]`). src/hook.cpp defines the hold, so this header names no backend type. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <cstddef> | ||
| 13 | #include <cstdint> | ||
| 14 | #include <string_view> | ||
| 15 | |||
| 16 | namespace DetourModKit::detail | ||
| 17 | { | ||
| 18 | /// Why a @ref BackendCoordinatorHold holds nothing. | ||
| 19 | enum class CoordinatorHoldRefusal : std::uint8_t | ||
| 20 | { | ||
| 21 | None, | ||
| 22 | /// Another thread held the coordinator for one backend wait slice. Only this cause clears by itself. | ||
| 23 | Timeout, | ||
| 24 | Poisoned, | ||
| 25 | Incompatible, | ||
| 26 | Unavailable, | ||
| 27 | }; | ||
| 28 | |||
| 29 | /// The log fragment that names @p refusal. | ||
| 30 | [[nodiscard]] constexpr std::string_view | ||
| 31 | 5 | coordinator_hold_refusal_description(CoordinatorHoldRefusal refusal) noexcept | |
| 32 | { | ||
| 33 | 5 | switch (refusal) | |
| 34 | { | ||
| 35 | ✗ | case CoordinatorHoldRefusal::Timeout: | |
| 36 | ✗ | return "the process coordinator wait timed out"; | |
| 37 | ✗ | case CoordinatorHoldRefusal::Poisoned: | |
| 38 | ✗ | return "the process coordinator is poisoned"; | |
| 39 | 4 | case CoordinatorHoldRefusal::Incompatible: | |
| 40 | 4 | return "the process coordinator is incompatible"; | |
| 41 | 1 | case CoordinatorHoldRefusal::Unavailable: | |
| 42 | 1 | return "the process coordinator is unavailable"; | |
| 43 | ✗ | case CoordinatorHoldRefusal::None: | |
| 44 | ✗ | break; | |
| 45 | } | ||
| 46 | ✗ | return {}; | |
| 47 | } | ||
| 48 | |||
| 49 | /// Selects the @ref BackendCoordinatorHold form that waits until held. The names label its one wait line. | ||
| 50 | struct WaitUntilHeld | ||
| 51 | { | ||
| 52 | std::string_view where; | ||
| 53 | std::string_view name; | ||
| 54 | }; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * @class BackendCoordinatorHold | ||
| 58 | * @brief RAII acquisition of the backend process coordinator. | ||
| 59 | * @details The owning thread acquires again without a wait. A refused hold reports false and holds nothing. | ||
| 60 | * Position 4 of the hook lock order (docs/design/hooking.md `[B-109]`): a holder must not wait for a | ||
| 61 | * HookLedger target slot or reservation, take a counted module reference, or run a consumer callable. | ||
| 62 | * `HookInstallCoordinator.InstallToggleAndRetireKeepTheLockOrder` pins the order. | ||
| 63 | */ | ||
| 64 | class BackendCoordinatorHold | ||
| 65 | { | ||
| 66 | public: | ||
| 67 | /// Waits for one bounded backend wait slice. | ||
| 68 | BackendCoordinatorHold() noexcept; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * @brief Repeats the bounded backend wait while the cause is @ref CoordinatorHoldRefusal::Timeout, with no | ||
| 72 | * attempt count and no time cap. Any other cause refuses at once. | ||
| 73 | * @details After the first timed-out slice, logs one Warning that names @p wait and the holder thread. The | ||
| 74 | * line runs while this object holds no coordinator, so a wait that never ends stays visible. | ||
| 75 | */ | ||
| 76 | explicit BackendCoordinatorHold(WaitUntilHeld wait) noexcept; | ||
| 77 | |||
| 78 | ~BackendCoordinatorHold() noexcept; | ||
| 79 | BackendCoordinatorHold(const BackendCoordinatorHold &) = delete; | ||
| 80 | BackendCoordinatorHold &operator=(const BackendCoordinatorHold &) = delete; | ||
| 81 | |||
| 82 | /// True while this object holds the coordinator. | ||
| 83 | 34799 | [[nodiscard]] explicit operator bool() const noexcept { return m_held; } | |
| 84 | |||
| 85 | /// Why construction holds nothing, or @ref CoordinatorHoldRefusal::None. | ||
| 86 | 10 | [[nodiscard]] CoordinatorHoldRefusal refusal() const noexcept { return m_refusal; } | |
| 87 | |||
| 88 | /// Releases the coordinator before scope exit. A second call is a no-op. | ||
| 89 | void release() noexcept; | ||
| 90 | |||
| 91 | /// Bytes reserved for the backend object. src/hook.cpp asserts the backend's size and alignment against it. | ||
| 92 | static constexpr std::size_t STORAGE_BYTES = 2 * sizeof(void *); | ||
| 93 | |||
| 94 | private: | ||
| 95 | /// Constructs the backend coordinator for one bounded wait and records the outcome. | ||
| 96 | void acquire() noexcept; | ||
| 97 | |||
| 98 | alignas(void *) unsigned char m_storage[STORAGE_BYTES]{}; | ||
| 99 | bool m_constructed{false}; | ||
| 100 | bool m_held{false}; | ||
| 101 | CoordinatorHoldRefusal m_refusal{CoordinatorHoldRefusal::None}; | ||
| 102 | }; | ||
| 103 | } // namespace DetourModKit::detail | ||
| 104 | |||
| 105 | #endif // DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP | ||
| 106 |