include/DetourModKit/memory.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_MEMORY_HPP | ||
| 2 | #define DETOURMODKIT_MEMORY_HPP | ||
| 3 | |||
| 4 | #include <cstddef> | ||
| 5 | #include <cstdint> | ||
| 6 | #include <expected> | ||
| 7 | #include <string> | ||
| 8 | #include <string_view> | ||
| 9 | |||
| 10 | namespace DetourModKit | ||
| 11 | { | ||
| 12 | class Logger; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * @enum MemoryError | ||
| 16 | * @brief Error codes for memory operation failures. | ||
| 17 | */ | ||
| 18 | enum class MemoryError | ||
| 19 | { | ||
| 20 | NullTargetAddress, | ||
| 21 | NullSourceBytes, | ||
| 22 | ProtectionChangeFailed, | ||
| 23 | ProtectionRestoreFailed | ||
| 24 | }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * @brief Converts a MemoryError to a human-readable string. | ||
| 28 | * @param error The error code. | ||
| 29 | * @return A string view describing the error. | ||
| 30 | */ | ||
| 31 | 5 | constexpr std::string_view memory_error_to_string(MemoryError error) | |
| 32 | { | ||
| 33 |
5/5✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 2 → 7 taken 1 time.
|
5 | switch (error) |
| 34 | { | ||
| 35 | 1 | case MemoryError::NullTargetAddress: | |
| 36 | 1 | return "Target address is null"; | |
| 37 | 1 | case MemoryError::NullSourceBytes: | |
| 38 | 1 | return "Source bytes pointer is null"; | |
| 39 | 1 | case MemoryError::ProtectionChangeFailed: | |
| 40 | 1 | return "Failed to change memory protection"; | |
| 41 | 1 | case MemoryError::ProtectionRestoreFailed: | |
| 42 | 1 | return "Failed to restore original memory protection"; | |
| 43 | 1 | default: | |
| 44 | 1 | return "Unknown memory error"; | |
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | // Memory cache configuration defaults | ||
| 49 | inline constexpr size_t DEFAULT_CACHE_SIZE = 256; | ||
| 50 | inline constexpr unsigned int DEFAULT_CACHE_EXPIRY_MS = 50; | ||
| 51 | inline constexpr size_t MIN_CACHE_SIZE = 1; | ||
| 52 | inline constexpr size_t DEFAULT_CACHE_SHARD_COUNT = 16; | ||
| 53 | inline constexpr size_t DEFAULT_MAX_CACHE_SIZE_MULTIPLIER = 2; | ||
| 54 | |||
| 55 | namespace Memory | ||
| 56 | { | ||
| 57 | /** | ||
| 58 | * @brief Initializes the memory region cache with specified parameters. | ||
| 59 | * @details Sets up an internal cache to store information about memory regions, | ||
| 60 | * reducing overhead of frequent VirtualQuery system calls. | ||
| 61 | * @param cache_size The desired number of entries in the cache. Defaults to 256. | ||
| 62 | * @param expiry_ms Cache entry expiry time in milliseconds. Defaults to 50ms. | ||
| 63 | * @param shard_count Number of cache shards for concurrent access. Defaults to 16. | ||
| 64 | * @return true if the cache is ready for use (newly or previously initialized), false on failure. | ||
| 65 | * @note Only the first call configures the cache; subsequent calls return true without reconfiguring. | ||
| 66 | * To reconfigure, call shutdown_cache() first. | ||
| 67 | * @note Starts a background cleanup thread that runs periodically. | ||
| 68 | */ | ||
| 69 | bool init_cache(size_t cache_size = DEFAULT_CACHE_SIZE, | ||
| 70 | unsigned int expiry_ms = DEFAULT_CACHE_EXPIRY_MS, | ||
| 71 | size_t shard_count = DEFAULT_CACHE_SHARD_COUNT); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * @brief Clears all entries from the memory region cache. | ||
| 75 | * @details Invalidates all currently cached memory region information. | ||
| 76 | * The background cleanup thread continues running. | ||
| 77 | */ | ||
| 78 | void clear_cache(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * @brief Shuts down the memory cache and stops the background cleanup thread. | ||
| 82 | * @details Call this before program exit to cleanly terminate the cleanup thread. | ||
| 83 | * After calling shutdown, the cache cannot be reused without re-initialization. | ||
| 84 | */ | ||
| 85 | void shutdown_cache(); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * @brief Retrieves statistics about the memory cache usage. | ||
| 89 | * @details Returns cache hits, misses, and hit rate information. | ||
| 90 | * Statistics are only available in debug builds. | ||
| 91 | * @return std::string A human-readable string of cache statistics. | ||
| 92 | */ | ||
| 93 | std::string get_cache_stats(); | ||
| 94 | |||
| 95 | /** | ||
| 96 | * @brief Invalidates cache entries that overlap with the specified address range. | ||
| 97 | * @details Used to force re-query of memory region info after external changes | ||
| 98 | * such as VirtualProtect calls by other code. | ||
| 99 | * @param address Starting address of the range to invalidate. | ||
| 100 | * @param size Size of the range to invalidate. | ||
| 101 | */ | ||
| 102 | void invalidate_range(const void *address, size_t size); | ||
| 103 | |||
| 104 | /** | ||
| 105 | * @brief Checks if a specified memory region is readable. | ||
| 106 | * @details Verifies if the memory range has read permissions and is committed. | ||
| 107 | * @param address Starting address of the memory region. | ||
| 108 | * @param size Number of bytes in the memory region to check. | ||
| 109 | * @return true if the entire region is readable, false otherwise. | ||
| 110 | */ | ||
| 111 | bool is_readable(const void *address, size_t size); | ||
| 112 | |||
| 113 | /** | ||
| 114 | * @enum ReadableStatus | ||
| 115 | * @brief Tri-state result for non-blocking readability checks. | ||
| 116 | */ | ||
| 117 | enum class ReadableStatus | ||
| 118 | { | ||
| 119 | Readable, | ||
| 120 | NotReadable, | ||
| 121 | Unknown | ||
| 122 | }; | ||
| 123 | |||
| 124 | /** | ||
| 125 | * @brief Non-blocking readability check that avoids contention on shared locks. | ||
| 126 | * @details Attempts a try-lock on the cache shard. Returns Unknown if the lock | ||
| 127 | * cannot be acquired, allowing callers on latency-sensitive threads to | ||
| 128 | * fall back to SEH instead of stalling. | ||
| 129 | * @param address Starting address of the memory region. | ||
| 130 | * @param size Number of bytes in the memory region to check. | ||
| 131 | * @return ReadableStatus indicating readable, not readable, or unknown (lock busy). | ||
| 132 | */ | ||
| 133 | ReadableStatus is_readable_nonblocking(const void *address, size_t size); | ||
| 134 | |||
| 135 | /** | ||
| 136 | * @brief Reads a pointer-sized value at (base + offset), returning 0 on fault. | ||
| 137 | * @details On MSVC, uses SEH (__try/__except) to catch access violations with | ||
| 138 | * zero overhead on the success path. On MinGW, falls back to a single | ||
| 139 | * VirtualQuery guard before dereferencing (no cache interaction). | ||
| 140 | * Suitable for hot paths that already manage their own error recovery. | ||
| 141 | * @param base The base address to read from. | ||
| 142 | * @param offset Byte offset added to base before dereferencing. | ||
| 143 | * @return The pointer-sized value at the address, or 0 if the read faults. | ||
| 144 | */ | ||
| 145 | uintptr_t read_ptr_unsafe(uintptr_t base, ptrdiff_t offset) noexcept; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * @brief Checks if a specified memory region is writable. | ||
| 149 | * @details Verifies if the memory range has write permissions and is committed. | ||
| 150 | * @param address Starting address of the memory region. | ||
| 151 | * @param size Number of bytes in the memory region to check. | ||
| 152 | * @return true if the entire region is writable, false otherwise. | ||
| 153 | */ | ||
| 154 | bool is_writable(void *address, size_t size); | ||
| 155 | |||
| 156 | /** | ||
| 157 | * @brief Writes a sequence of bytes to a target memory address. | ||
| 158 | * @details Handles changing memory protection, performs the write operation, | ||
| 159 | * and restores original protection. Also flushes instruction cache. | ||
| 160 | * Automatically invalidates the affected cache range. | ||
| 161 | * @param targetAddress Destination memory address. | ||
| 162 | * @param sourceBytes Pointer to the source buffer containing data to write. | ||
| 163 | * @param numBytes Number of bytes to write. | ||
| 164 | * @param logger Reference to a Logger instance for error reporting. | ||
| 165 | * @return std::expected<void, MemoryError> on success, or the specific error on failure. | ||
| 166 | */ | ||
| 167 | [[nodiscard]] std::expected<void, MemoryError> write_bytes(std::byte *targetAddress, const std::byte *sourceBytes, size_t numBytes, Logger &logger); | ||
| 168 | } // namespace Memory | ||
| 169 | } // namespace DetourModKit | ||
| 170 | |||
| 171 | #endif // DETOURMODKIT_MEMORY_HPP | ||
| 172 |