src/scanner_parallel.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file scanner_parallel.cpp | ||
| 3 | * @brief Opt-in fork-join batch resolvers: scan many compiled patterns, or resolve many cascades, concurrently. | ||
| 4 | * | ||
| 5 | * Sibling translation unit of the scanner module (scanner.cpp / scanner_cascade.cpp), sharing the public scanner.hpp | ||
| 6 | * surface and the internal scanner_internal.hpp entry points. It adds no new scan or resolve primitive: each batch item | ||
| 7 | * is dispatched to an existing serial entry point on a worker thread, and results are gathered in input order by the | ||
| 8 | * shared fork-join driver in fork_join.hpp. scan_regions_batch / scan_module_batch fan the per-pattern region walk | ||
| 9 | * (scan_executable_regions / scan_readable_regions for the whole process, detail::scan_module_* for one image) across | ||
| 10 | * workers; resolve_cascade_batch fans the cascade resolvers (resolve_cascade / resolve_cascade_in_module and their | ||
| 11 | * prologue-fallback variants). | ||
| 12 | * | ||
| 13 | * Correctness rests on read-only sharing of caller-owned inputs. The batch scanners rely on the CompiledPattern | ||
| 14 | * immutability contract: find_pattern and the region walk take the pattern by const reference and never write back (an | ||
| 15 | * un-anchored pattern recomputes its anchor into a local), so a compiled pattern is safe to read concurrently from | ||
| 16 | * many threads without cloning. The cascade resolvers compile each candidate into a per-call local CompiledPattern, so | ||
| 17 | * concurrent requests share only the immutable candidate tables. The runtime CPUID feature caches and the Logger are | ||
| 18 | * thread-safe, and VirtualQuery is an OS-level read, so concurrent serial work shares no mutable state. | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include "DetourModKit/scanner.hpp" | ||
| 22 | #include "DetourModKit/memory.hpp" | ||
| 23 | |||
| 24 | #include "fork_join.hpp" | ||
| 25 | #include "scanner_internal.hpp" | ||
| 26 | |||
| 27 | #include <cstddef> | ||
| 28 | #include <expected> | ||
| 29 | #include <optional> | ||
| 30 | #include <span> | ||
| 31 | #include <vector> | ||
| 32 | |||
| 33 | namespace DetourModKit | ||
| 34 | { | ||
| 35 | namespace Scanner | ||
| 36 | { | ||
| 37 | 13 | std::vector<const std::byte *> scan_regions_batch(std::span<const BatchScanItem> items, ScannerKind kind, | |
| 38 | std::size_t max_workers) | ||
| 39 | { | ||
| 40 | return DetourModKit::detail::run_fork_join<BatchScanItem, const std::byte *>( | ||
| 41 | items, max_workers, | ||
| 42 | 79 | [kind](const BatchScanItem &item) -> const std::byte * | |
| 43 | { | ||
| 44 | // The null check guards the *item.pattern dereference below. An empty pattern or | ||
| 45 | // occurrence == 0 is intentionally not re-checked here: the serial scanners already fail both | ||
| 46 | // closed (their pattern.empty() / occurrence == 0 guards return nullptr). | ||
| 47 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 78 times.
|
79 | if (item.pattern == nullptr) |
| 48 | { | ||
| 49 | 1 | return nullptr; | |
| 50 | } | ||
| 51 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 77 times.
|
78 | return (kind == ScannerKind::Readable) ? scan_readable_regions(*item.pattern, item.occurrence) |
| 52 | 78 | : scan_executable_regions(*item.pattern, item.occurrence); | |
| 53 | }, | ||
| 54 | 92 | [](const BatchScanItem &) noexcept -> const std::byte * { return nullptr; }); | |
| 55 | } | ||
| 56 | |||
| 57 | 2 | std::vector<const std::byte *> scan_module_batch(std::span<const BatchScanItem> items, | |
| 58 | Memory::ModuleRange range, ScannerKind kind, | ||
| 59 | std::size_t max_workers) | ||
| 60 | { | ||
| 61 | return DetourModKit::detail::run_fork_join<BatchScanItem, const std::byte *>( | ||
| 62 | items, max_workers, | ||
| 63 | 10 | [kind, range](const BatchScanItem &item) -> const std::byte * | |
| 64 | { | ||
| 65 | // The null check guards the *item.pattern dereference below. An empty pattern or | ||
| 66 | // occurrence == 0 is intentionally not re-checked here: the serial module scanners already fail | ||
| 67 | // both closed (their pattern.empty() / occurrence == 0 guards return nullptr). | ||
| 68 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 10 times.
|
10 | if (item.pattern == nullptr) |
| 69 | { | ||
| 70 | ✗ | return nullptr; | |
| 71 | } | ||
| 72 | 10 | return (kind == ScannerKind::Readable) | |
| 73 |
1/2✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 6 not taken.
|
10 | ? detail::scan_module_readable(*item.pattern, range, item.occurrence) |
| 74 | 10 | : detail::scan_module_executable(*item.pattern, range, item.occurrence); | |
| 75 | }, | ||
| 76 | 12 | [](const BatchScanItem &) noexcept -> const std::byte * { return nullptr; }); | |
| 77 | } | ||
| 78 | |||
| 79 | std::vector<std::expected<ResolveHit, ResolveError>> | ||
| 80 | 7 | resolve_cascade_batch(std::span<const CascadeRequest> requests, std::size_t max_workers) | |
| 81 | { | ||
| 82 | using CascadeResult = std::expected<ResolveHit, ResolveError>; | ||
| 83 | return DetourModKit::detail::run_fork_join<CascadeRequest, CascadeResult>( | ||
| 84 | requests, max_workers, | ||
| 85 | 70 | [](const CascadeRequest &request) -> CascadeResult | |
| 86 | { | ||
| 87 |
2/2✓ Branch 3 → 4 taken 68 times.
✓ Branch 3 → 10 taken 2 times.
|
70 | if (request.range) |
| 88 | { | ||
| 89 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 8 taken 67 times.
|
68 | if (request.prologue_fallback) |
| 90 | { | ||
| 91 | 1 | return resolve_cascade_in_module_with_prologue_fallback(request.candidates, request.label, | |
| 92 | 2 | *request.range); | |
| 93 | } | ||
| 94 | 67 | return resolve_cascade_in_module(request.candidates, request.label, *request.range); | |
| 95 | } | ||
| 96 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
|
2 | if (request.prologue_fallback) |
| 97 | { | ||
| 98 | ✗ | return resolve_cascade_with_prologue_fallback(request.candidates, request.label); | |
| 99 | } | ||
| 100 | 2 | return resolve_cascade(request.candidates, request.label, request.kind); | |
| 101 | }, | ||
| 102 | 70 | [](const CascadeRequest &) noexcept -> CascadeResult | |
| 103 | 77 | { return std::unexpected(ResolveError::NoMatch); }); | |
| 104 | } | ||
| 105 | } // namespace Scanner | ||
| 106 | } // namespace DetourModKit | ||
| 107 |