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 |