GCC Code Coverage Report


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

src/memory_internal.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_MEMORY_INTERNAL_HPP
2 #define DETOURMODKIT_MEMORY_INTERNAL_HPP
3
4 /**
5 * @file memory_internal.hpp
6 * @brief Shared fault-handling primitives for the SEH-guarded foreign-read paths in memory.cpp, scanner.cpp, and
7 * string_xref.cpp.
8 *
9 * The probe paths that read foreign (host-owned) memory -- the pointer-chain walks in memory.cpp and the protection-
10 * gated region/window sweeps in scanner.cpp and string_xref.cpp -- all run their reads inside a Structured Exception
11 * Handling frame on MSVC. They must agree on exactly which exception codes belong to such a read so the __except
12 * filters stay identical: a single predicate keeps that set in one place and makes it unit-testable in isolation. The
13 * codes are spelled as numeric literals (matching <winnt.h>) so this header stays free of <windows.h> and can be
14 * included by any TU -- mirroring the way scanner_internal.hpp keeps the Windows page-protection masks private to
15 * scanner.cpp. The declarations are internal to the build and are NOT part of the installed public surface.
16 */
17
18 #include <cstdint>
19
20 namespace DetourModKit
21 {
22 namespace Memory
23 {
24 namespace detail
25 {
26 /**
27 * @brief True when @p exception_code is a fault a guarded foreign read may legitimately raise and must
28 * swallow (reporting read failure) rather than let escape and terminate the host.
29 * @details The accepted set, spelled as literals so this header needs no <windows.h>:
30 * - 0xC0000005 EXCEPTION_ACCESS_VIOLATION -- the page is unmapped / PAGE_NOACCESS, or the access
31 * collided with a concurrent decommit / reprotect after the probe's readability gate passed.
32 * - 0x80000001 STATUS_GUARD_PAGE_VIOLATION -- first touch of a PAGE_GUARD page.
33 * - 0xC0000006 EXCEPTION_IN_PAGE_ERROR -- a file-backed or image-mapped page failed to page
34 * in (for example an RTTI / section walk of a module whose backing view was invalidated).
35 * Omitting this code let the fault continue the handler search and terminate the host, violating
36 * the "false on any fault" contract every probe documents.
37 * Any other code (illegal instruction, breakpoint, stack overflow, ...) did not originate from the
38 * probe's own read, so the filter must continue the search and let the host's real handler see it.
39 * @param exception_code The value returned by GetExceptionCode() inside an __except filter.
40 * @return true to execute the handler (swallow and fail closed); false to continue the search.
41 */
42 8475 [[nodiscard]] constexpr bool is_guarded_read_fault(unsigned long exception_code) noexcept
43 {
44 return exception_code == 0xC0000005ul // EXCEPTION_ACCESS_VIOLATION
45
2/2
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 3 times.
9 || exception_code == 0x80000001ul // STATUS_GUARD_PAGE_VIOLATION
46
4/4
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 8466 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 5 times.
8484 || exception_code == 0xC0000006ul; // EXCEPTION_IN_PAGE_ERROR
47 }
48
49 #if !defined(_MSC_VER) && defined(_WIN64)
50 /**
51 * @brief Runs @p fn(@p ctx) with the process-wide vectored read guard armed over [@p lo, @p hi).
52 * @details MinGW x64 has no frame-based __try / __except, so a bulk in-place foreign read -- the scanner's
53 * memchr / SIMD region sweep -- cannot wrap itself in SEH the way the MSVC path does. This routes
54 * such a read through the same vectored exception handler, thread-local guard slot, and drain
55 * epoch the seh_read_bytes path uses: the guard is armed for [lo, hi), @p fn performs the read,
56 * and a guarded read fault (is_guarded_read_fault) inside that range is turned into a clean
57 * failure (the handler longjmps back) instead of terminating the host. @p fn must be a
58 * self-contained read with no resources that need unwinding, because a guarded fault abandons its
59 * frame via __builtin_longjmp without running destructors -- exactly the contract the copy-based
60 * guard relies on. When the handler could not be installed @p fn is not run; callers treat false
61 * as a skipped/faulted range and fail uniqueness-sensitive work closed.
62 * @param lo First byte of the foreign range @p fn will read.
63 * @param hi One past the last byte of that range. An empty or wrapping range (hi <= lo) runs @p fn
64 * directly because there is no foreign byte span to guard.
65 * @param fn The read to perform; must be noexcept and must not throw.
66 * @param ctx Opaque pointer forwarded to @p fn.
67 * @return true if @p fn completed without a guarded read fault; false if a fault inside [lo, hi) was
68 * swallowed, in which case @p fn did not run to completion.
69 */
70 [[nodiscard]] bool run_guarded_region(std::uintptr_t lo, std::uintptr_t hi, void (*fn)(void *) noexcept,
71 void *ctx) noexcept;
72 #endif
73 } // namespace detail
74 } // namespace Memory
75 } // namespace DetourModKit
76
77 #endif // DETOURMODKIT_MEMORY_INTERNAL_HPP
78