src/scan_matching.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file scan_matching.cpp | ||
| 3 | * @brief Public single-pattern matching: scan(), unchecked::find_pattern(), active_simd_level(), and | ||
| 4 | * is_likely_function_prologue(). | ||
| 5 | * @details scan.hpp owns each contract. | ||
| 6 | */ | ||
| 7 | |||
| 8 | #include "DetourModKit/scan.hpp" | ||
| 9 | |||
| 10 | #include "internal/memory_guarded.hpp" | ||
| 11 | #include "internal/scan_engine.hpp" | ||
| 12 | #include "internal/scan_exclusions.hpp" | ||
| 13 | #include "internal/scan_pages.hpp" | ||
| 14 | #include "internal/scan_shared.hpp" | ||
| 15 | |||
| 16 | #include <cstddef> | ||
| 17 | #include <cstdint> | ||
| 18 | #include <new> | ||
| 19 | #include <span> | ||
| 20 | |||
| 21 | namespace DetourModKit | ||
| 22 | { | ||
| 23 | namespace scan | ||
| 24 | { | ||
| 25 | 4 | SimdLevel active_simd_level() noexcept | |
| 26 | { | ||
| 27 | 4 | return detail::active_simd_level(); | |
| 28 | } | ||
| 29 | |||
| 30 | 14 | Result<Address> scan(const Pattern &pattern, Region scope, std::size_t occurrence, Pages pages) noexcept | |
| 31 | { | ||
| 32 | 14 | return scan(pattern, scope, std::span<const Region>{}, occurrence, pages); | |
| 33 | } | ||
| 34 | |||
| 35 | 17 | Result<Address> scan( | |
| 36 | const Pattern &pattern, | ||
| 37 | Region scope, | ||
| 38 | std::span<const Region> exclusions, | ||
| 39 | std::size_t occurrence, | ||
| 40 | Pages pages | ||
| 41 | ) noexcept | ||
| 42 | { | ||
| 43 | 17 | if (occurrence == 0) | |
| 44 | { | ||
| 45 | ✗ | return std::unexpected(Error{ErrorCode::NoMatch, "scan::scan"}); | |
| 46 | } | ||
| 47 | 17 | if (pages != Pages::Readable && pages != Pages::Executable) | |
| 48 | { | ||
| 49 | 1 | return std::unexpected(Error{ErrorCode::InvalidArg, "scan::scan"}); | |
| 50 | } | ||
| 51 | 16 | const detail::ModuleSpan range = detail::module_span(scope); | |
| 52 | 16 | if (!range.valid()) | |
| 53 | { | ||
| 54 | 1 | return std::unexpected(Error{ErrorCode::InvalidRange, "scan::scan"}); | |
| 55 | } | ||
| 56 | 15 | if (!detail::readable_scan_is_authoritative(range, pages, exclusions)) | |
| 57 | { | ||
| 58 | 2 | return std::unexpected(Error{ErrorCode::NotAuthoritative, "scan::scan"}); | |
| 59 | } | ||
| 60 | try | ||
| 61 | { | ||
| 62 | 13 | detail::ScanExclusions excluded; | |
| 63 | // Only spans the sweep will actually read can affect its result, so dropping the rest keeps a caller | ||
| 64 | // that declares many copies from exhausting the set over spans that were never in play. | ||
| 65 | 13 | excluded.restrict_to(range.base, range.end); | |
| 66 | 13 | detail::add_pattern_storage(excluded, pattern); | |
| 67 | 13 | detail::add_regions(excluded, exclusions); | |
| 68 | 13 | if (excluded.overflowed()) | |
| 69 | { | ||
| 70 | // More declared spans than the set can hold: some query storage would go unexcluded, so no result | ||
| 71 | // from this scan is trustworthy. Fail closed instead of silently narrowing the exclusion. | ||
| 72 | ✗ | return std::unexpected(Error{ErrorCode::NotAuthoritative, "scan::scan"}); | |
| 73 | } | ||
| 74 | |||
| 75 | 13 | const detail::HaystackHistogram histogram = detail::sample_haystack(scope); | |
| 76 | 13 | const detail::EnginePattern compiled = detail::to_engine_pattern(pattern, histogram); | |
| 77 | 13 | const detail::MatchResult result = detail::scan_module_pages( | |
| 78 | compiled, | ||
| 79 | range, | ||
| 80 | pages, | ||
| 81 | 13 | detail::ScanQuery{ | |
| 82 | .occurrence = occurrence, | ||
| 83 | .count_beyond = false, | ||
| 84 | .exclusions = &excluded, | ||
| 85 | } | ||
| 86 | ); | ||
| 87 | // A truncated sweep never visited part of the scope, so an earlier occurrence may hide there: the match | ||
| 88 | // it did find is not provably the Nth one. That makes truncation fatal to the result whether or not a | ||
| 89 | // match was found. The two causes are distinct caller problems and stay distinct codes: a concurrent | ||
| 90 | // unmap of the scanned range versus a pattern whose bounded jumps are too broad to search exhaustively. | ||
| 91 | 13 | if (result.budget_exhausted) | |
| 92 | { | ||
| 93 | 2 | return std::unexpected(Error{ErrorCode::BudgetExceeded, "scan::scan"}); | |
| 94 | } | ||
| 95 | 11 | if (result.incomplete) | |
| 96 | { | ||
| 97 | ✗ | return std::unexpected(Error{ErrorCode::IncompleteScan, "scan::scan"}); | |
| 98 | } | ||
| 99 | 11 | if (result.match == nullptr) | |
| 100 | { | ||
| 101 | 4 | return std::unexpected(Error{ErrorCode::NoMatch, "scan::scan"}); | |
| 102 | } | ||
| 103 | 7 | return Address{reinterpret_cast<std::uintptr_t>(result.match)}; | |
| 104 | 13 | } | |
| 105 | ✗ | catch (const std::bad_alloc &) | |
| 106 | { | ||
| 107 | ✗ | return std::unexpected(Error{ErrorCode::OutOfMemory, "scan::scan"}); | |
| 108 | ✗ | } | |
| 109 | } | ||
| 110 | |||
| 111 | 17 | bool is_likely_function_prologue(Address addr) noexcept | |
| 112 | { | ||
| 113 | 17 | if (!addr) | |
| 114 | { | ||
| 115 | 1 | return false; | |
| 116 | } | ||
| 117 | |||
| 118 | 16 | const auto b0 = detail::guarded_read<std::uint8_t>(addr.raw()); | |
| 119 | 16 | if (!b0) | |
| 120 | { | ||
| 121 | 1 | return false; | |
| 122 | } | ||
| 123 | |||
| 124 | // is_likely_function_prologue in scan.hpp owns the poison list. | ||
| 125 | 15 | return *b0 != 0x00 && *b0 != 0xCC && *b0 != 0xC2 && *b0 != 0xC3; | |
| 126 | } | ||
| 127 | |||
| 128 | namespace unchecked | ||
| 129 | { | ||
| 130 | 266 | const std::byte *find_pattern(Region region, const Pattern &pattern, std::size_t occurrence) noexcept | |
| 131 | { | ||
| 132 | 266 | if (region.base.raw() == 0 || region.size == 0 || occurrence == 0) | |
| 133 | { | ||
| 134 | 1 | return nullptr; | |
| 135 | } | ||
| 136 | try | ||
| 137 | { | ||
| 138 | // No page filter and no haystack anchor override: the caller owns readability. | ||
| 139 | 265 | const std::size_t anchor = pattern.has_anchor() ? pattern.anchor_index() : pattern.size(); | |
| 140 | 265 | const detail::EnginePattern compiled = detail::engine_pattern_from(pattern, anchor); | |
| 141 | 265 | return detail::find_pattern(region.base.ptr<const std::byte>(), region.size, compiled, occurrence); | |
| 142 | 265 | } | |
| 143 | ✗ | catch (const std::bad_alloc &) | |
| 144 | { | ||
| 145 | ✗ | return nullptr; | |
| 146 | ✗ | } | |
| 147 | } | ||
| 148 | } // namespace unchecked | ||
| 149 | } // namespace scan | ||
| 150 | } // namespace DetourModKit | ||
| 151 |