src/scan_candidates.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file scan_candidates.cpp | ||
| 3 | * @brief The candidate-ladder vocabulary functions: order_candidates() and borrow(). | ||
| 4 | * @details The Candidate factories and accessors are inline in scan.hpp (the variant payload model), so this TU owns | ||
| 5 | * only the ordering permutation and the borrowed-request packer. order_candidates is pure index math over the | ||
| 6 | * variant tiers; borrow packs the borrowed views into a ScanRequest. Both are noexcept and allocate nothing. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include "DetourModKit/scan.hpp" | ||
| 10 | |||
| 11 | #include <algorithm> | ||
| 12 | #include <cstddef> | ||
| 13 | #include <span> | ||
| 14 | |||
| 15 | namespace DetourModKit | ||
| 16 | { | ||
| 17 | namespace scan | ||
| 18 | { | ||
| 19 | std::size_t | ||
| 20 | 392 | order_candidates(CandidateOrder order, std::span<const Candidate> ladder, std::span<std::size_t> out) noexcept | |
| 21 | { | ||
| 22 | 392 | const std::size_t count = std::min(ladder.size(), out.size()); | |
| 23 | // This noexcept helper cannot report InvalidArg, so an unknown value preserves declaration order and never | ||
| 24 | // selects the UniqueFirst promotion. | ||
| 25 |
2/2✓ Branch 5 → 6 taken 382 times.
✓ Branch 5 → 11 taken 9 times.
|
391 | if (order != CandidateOrder::UniqueFirst) |
| 26 | { | ||
| 27 |
2/2✓ Branch 9 → 7 taken 394 times.
✓ Branch 9 → 10 taken 382 times.
|
776 | for (std::size_t i = 0; i < count; ++i) |
| 28 | { | ||
| 29 | 394 | out[i] = i; | |
| 30 | } | ||
| 31 | 382 | return count; | |
| 32 | } | ||
| 33 | |||
| 34 | // UniqueFirst: three stable passes over the declared order. Every candidate falls into exactly one pass, so | ||
| 35 | // the result is a permutation of [0, count). | ||
| 36 | 9 | std::size_t written = 0; | |
| 37 | 27 | const auto emit = [&](auto predicate) | |
| 38 | { | ||
| 39 |
15/18auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}) const:
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 13 taken 9 times.
✓ Branch 11 → 12 taken 13 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 3 taken 13 times.
✓ Branch 14 → 15 taken 9 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}) const:
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 13 taken 9 times.
✓ Branch 11 → 12 taken 13 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 3 taken 13 times.
✓ Branch 14 → 15 taken 9 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}) const:
✓ Branch 10 → 11 taken 13 times.
✗ Branch 10 → 13 not taken.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 13 taken 9 times.
✓ Branch 14 → 3 taken 4 times.
✓ Branch 14 → 15 taken 9 times.
|
57 | for (std::size_t i = 0; i < ladder.size() && written < count; ++i) |
| 40 | { | ||
| 41 |
6/6auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}) const:
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 8 taken 11 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}) const:
✓ Branch 5 → 6 taken 9 times.
✓ Branch 5 → 8 taken 4 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}) const:
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 8 taken 2 times.
|
30 | if (predicate(ladder[i])) |
| 42 | { | ||
| 43 | 13 | out[written] = i; | |
| 44 | 13 | ++written; | |
| 45 | } | ||
| 46 | } | ||
| 47 | 36 | }; | |
| 48 | 4 | const auto is_byte_mode = [](const Candidate &candidate) | |
| 49 |
3/4✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 3 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
|
4 | { return candidate.mode() == Mode::Direct || candidate.mode() == Mode::RipRelative; }; |
| 50 | 16 | const auto is_anchored_byte = [&](const Candidate &candidate) | |
| 51 | { | ||
| 52 | // A byte tier (Direct / RipRelative) whose compiled Pattern carries a fully-known rarest byte the | ||
| 53 | // prefilter can anchor on; that makes the scan far more selective than a wildcard-led pattern. | ||
| 54 |
2/2✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 5 taken 3 times.
|
16 | if (const DirectPattern *direct = candidate.as_direct()) |
| 55 | { | ||
| 56 | 13 | return direct->pattern.has_anchor(); | |
| 57 | } | ||
| 58 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 2 times.
|
3 | if (const RipRelativePattern *rip = candidate.as_rip_relative()) |
| 59 | { | ||
| 60 | 1 | return rip->pattern.has_anchor(); | |
| 61 | } | ||
| 62 | 2 | return false; | |
| 63 | }; | ||
| 64 | |||
| 65 | // Pass 1: the unique-only text tiers, which fail closed on ambiguity by construction. | ||
| 66 | 9 | emit([](const Candidate &candidate) | |
| 67 |
4/4✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 11 times.
|
13 | { return candidate.mode() == Mode::RttiVtable || candidate.mode() == Mode::StringXref; }); |
| 68 | // Pass 2: anchored byte patterns (a fully-known rarest byte makes the scan far more selective). | ||
| 69 | 22 | emit([&](const Candidate &candidate) { return is_anchored_byte(candidate); }); | |
| 70 | // Pass 3: the remaining byte patterns (no fully-known byte to anchor on). | ||
| 71 |
4/4✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 1 time.
|
13 | emit([&](const Candidate &candidate) { return is_byte_mode(candidate) && !is_anchored_byte(candidate); }); |
| 72 | 10 | return written; | |
| 73 | } | ||
| 74 | |||
| 75 | 9 | ScanRequest borrow( | |
| 76 | std::span<const Candidate> ladder, | ||
| 77 | std::string_view label, | ||
| 78 | Region scope, | ||
| 79 | FallbackPolicy fallback_policy, | ||
| 80 | FallbackWitness fallback_witness, | ||
| 81 | bool require_unique, | ||
| 82 | CandidateOrder order, | ||
| 83 | Pages pages | ||
| 84 | ) noexcept | ||
| 85 | { | ||
| 86 | return ScanRequest{ | ||
| 87 | .ladder = ladder, | ||
| 88 | .label = label, | ||
| 89 | .scope = scope, | ||
| 90 | .fallback_policy = fallback_policy, | ||
| 91 | .fallback_witness = fallback_witness, | ||
| 92 | .require_unique = require_unique, | ||
| 93 | .order = order, | ||
| 94 | .pages = pages, | ||
| 95 | 9 | }; | |
| 96 | } | ||
| 97 | |||
| 98 | 7 | ScanRequest borrow_code_target( | |
| 99 | std::span<const Candidate> ladder, | ||
| 100 | std::string_view label, | ||
| 101 | Region scope, | ||
| 102 | FallbackPolicy fallback_policy, | ||
| 103 | FallbackWitness fallback_witness | ||
| 104 | ) noexcept | ||
| 105 | { | ||
| 106 | // The code-target policy: scan only executable pages (an instruction signature must not alias a byte run in | ||
| 107 | // data), promote the unique-only / anchored tiers first, and enable hooked-prologue recovery so a target | ||
| 108 | // another mod already inline-hooked is still resolved. The recovery strictness comes from the caller | ||
| 109 | // (WarnOnly by default, RequireIdentity + a witness to fail closed on an unconfirmed near-twin). | ||
| 110 | // require_unique stays true. Route through borrow() so the common ScanRequest fields are defined in one | ||
| 111 | // place, then require its final address to be execute-readable too: a RipRelative candidate can match code | ||
| 112 | // bytes but resolve its disp32 to data, and text tiers do not use the byte-page filter at all. | ||
| 113 | 7 | ScanRequest request = borrow( | |
| 114 | ladder, | ||
| 115 | label, | ||
| 116 | scope, | ||
| 117 | fallback_policy, | ||
| 118 | fallback_witness, | ||
| 119 | /*require_unique=*/true, | ||
| 120 | CandidateOrder::UniqueFirst, | ||
| 121 | Pages::Executable | ||
| 122 | ); | ||
| 123 | 7 | request.require_executable_result = true; | |
| 124 | 7 | return request; | |
| 125 | } | ||
| 126 | |||
| 127 | 2 | ScanRequest borrow_code_target_strict( | |
| 128 | std::span<const Candidate> ladder, | ||
| 129 | std::string_view label, | ||
| 130 | FallbackWitness fallback_witness, | ||
| 131 | Region scope | ||
| 132 | ) noexcept | ||
| 133 | { | ||
| 134 | // The strict code-target policy: identical to borrow_code_target but pinned to RequireIdentity, so a | ||
| 135 | // hooked-prologue recovery is trusted only when the witness confirms it and a coincidental near-twin fails | ||
| 136 | // closed. The witness parameter is non-defaulted so a call site states one, but its predicate can still be | ||
| 137 | // null, and RequireIdentity then fails closed on every recovery (the FallbackWitness contract). Route | ||
| 138 | // through borrow_code_target so the shared code-target fields (Pages::Executable, | ||
| 139 | // require_executable_result, UniqueFirst, require_unique) live in exactly one place. | ||
| 140 | 2 | return borrow_code_target(ladder, label, scope, FallbackPolicy::RequireIdentity, fallback_witness); | |
| 141 | } | ||
| 142 | } // namespace scan | ||
| 143 | } // namespace DetourModKit | ||
| 144 |