GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 12 / 0 / 12
Functions: 100.0% 1 / 0 / 1
Branches: 100.0% 5 / 0 / 5

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