src/scan_code_constant.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file scan_code_constant.cpp | ||
| 3 | * @brief Zydis-backed extraction of a constant encoded in engine machine code: read_code_constant(). | ||
| 4 | * @details The code-side twin of the RTTI self-heal: the CodeConstant's candidate ladder resolves to an instruction | ||
| 5 | * site (via scan::resolve), the live instruction is decoded, and the requested operand's immediate or memory | ||
| 6 | * displacement is returned as the CURRENT value. The caller's nominal is never a short-circuit, so a | ||
| 7 | * same-shape / different-value drift is reported as the new value. The CodeConstant's Candidate ladder | ||
| 8 | * resolves the site; Zydis is confined to this TU. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include "DetourModKit/scan.hpp" | ||
| 12 | |||
| 13 | #include "internal/scan_engine.hpp" | ||
| 14 | #include "internal/scan_pages.hpp" | ||
| 15 | #include "internal/scan_shared.hpp" | ||
| 16 | |||
| 17 | #include <Zydis/Zydis.h> | ||
| 18 | |||
| 19 | #include <algorithm> | ||
| 20 | #include <cstddef> | ||
| 21 | #include <cstdint> | ||
| 22 | #include <cstring> | ||
| 23 | #include <span> | ||
| 24 | |||
| 25 | namespace DetourModKit | ||
| 26 | { | ||
| 27 | namespace scan | ||
| 28 | { | ||
| 29 | namespace | ||
| 30 | { | ||
| 31 | // Narrows an already-64-bit-sign-extended value to @p byte_width bytes and re-sign-extends from that width, | ||
| 32 | // so a deliberately narrowed negative value (for example a disp8 of -1) stays negative instead of becoming | ||
| 33 | // a large positive number. @p byte_width 0 returns the value verbatim, since Zydis has already | ||
| 34 | // sign-extended immediates and displacements to 64 bits. | ||
| 35 | 121 | std::int64_t narrow_signed(std::int64_t value, std::uint8_t byte_width) noexcept | |
| 36 | { | ||
| 37 |
4/4✓ Branch 2 → 3 taken 24 times.
✓ Branch 2 → 4 taken 97 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 22 times.
|
121 | if (byte_width == 0 || byte_width >= sizeof(std::int64_t)) |
| 38 | { | ||
| 39 | 99 | return value; | |
| 40 | } | ||
| 41 | 22 | const unsigned bits = static_cast<unsigned>(byte_width) * 8u; | |
| 42 | 22 | const std::uint64_t mask = (std::uint64_t{1} << bits) - 1u; | |
| 43 | 22 | const std::uint64_t masked = static_cast<std::uint64_t>(value) & mask; | |
| 44 | 22 | const std::uint64_t sign_bit = std::uint64_t{1} << (bits - 1u); | |
| 45 | // Two's-complement sign-extension from the top bit of the chosen width. | ||
| 46 | 22 | const std::uint64_t extended = (masked ^ sign_bit) - sign_bit; | |
| 47 | 22 | return static_cast<std::int64_t>(extended); | |
| 48 | } | ||
| 49 | } // namespace | ||
| 50 | |||
| 51 | namespace | ||
| 52 | { | ||
| 53 | 141 | void overlay_snapshot( | |
| 54 | std::span<std::byte> destination, | ||
| 55 | std::uintptr_t destination_base, | ||
| 56 | std::span<const std::byte> source, | ||
| 57 | std::uintptr_t source_base | ||
| 58 | ) noexcept | ||
| 59 | { | ||
| 60 | 141 | std::size_t destination_offset = 0; | |
| 61 | 141 | std::size_t source_offset = 0; | |
| 62 |
2/2✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 7 taken 136 times.
|
141 | if (destination_base < source_base) |
| 63 | { | ||
| 64 | 5 | const std::uintptr_t delta = source_base - destination_base; | |
| 65 |
2/2✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 1 time.
|
5 | if (delta >= destination.size()) |
| 66 | { | ||
| 67 | 4 | return; | |
| 68 | } | ||
| 69 | 1 | destination_offset = static_cast<std::size_t>(delta); | |
| 70 | } | ||
| 71 | else | ||
| 72 | { | ||
| 73 | 136 | const std::uintptr_t delta = destination_base - source_base; | |
| 74 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 136 times.
|
136 | if (delta >= source.size()) |
| 75 | { | ||
| 76 | ✗ | return; | |
| 77 | } | ||
| 78 | 136 | source_offset = static_cast<std::size_t>(delta); | |
| 79 | } | ||
| 80 | |||
| 81 | const std::size_t overlap = | ||
| 82 | 137 | std::min(destination.size() - destination_offset, source.size() - source_offset); | |
| 83 | 137 | std::memcpy(destination.data() + destination_offset, source.data() + source_offset, overlap); | |
| 84 | } | ||
| 85 | |||
| 86 | 141 | [[nodiscard]] bool selector_still_resolves_site( | |
| 87 | const Candidate &candidate, | ||
| 88 | Region match_span, | ||
| 89 | Region physical_source, | ||
| 90 | std::uintptr_t decoded_site, | ||
| 91 | std::uintptr_t window_base, | ||
| 92 | std::span<const std::byte> window | ||
| 93 | ) | ||
| 94 | { | ||
| 95 | 141 | constexpr std::size_t MAX_EVIDENCE_SPAN = | |
| 96 | detail::MAX_PATTERN_BYTES + detail::MAX_PATTERN_JUMPS * detail::MAX_JUMP_SPAN; | ||
| 97 | 141 | constexpr std::size_t MAX_SOURCE_SPAN = MAX_EVIDENCE_SPAN + MAX_X86_INSTRUCTION_LENGTH; | |
| 98 | 141 | const Pattern *pattern = detail::byte_pattern_of(candidate); | |
| 99 | 141 | const std::uintptr_t span_base = match_span.base.raw(); | |
| 100 | 141 | const std::uintptr_t source_base = physical_source.base.raw(); | |
| 101 |
4/8✓ Branch 5 → 6 taken 141 times.
✗ Branch 5 → 9 not taken.
✓ Branch 6 → 7 taken 141 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 141 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 141 times.
|
141 | if (pattern == nullptr || span_base == 0 || match_span.size == 0 || match_span.size > MAX_EVIDENCE_SPAN) |
| 102 | { | ||
| 103 | ✗ | return false; | |
| 104 | } | ||
| 105 |
2/4✓ Branch 10 → 11 taken 141 times.
✗ Branch 10 → 13 not taken.
✓ Branch 11 → 12 taken 141 times.
✗ Branch 11 → 13 not taken.
|
141 | if (source_base != span_base || physical_source.size < match_span.size || |
| 106 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 141 times.
|
141 | physical_source.size > MAX_SOURCE_SPAN) |
| 107 | { | ||
| 108 | ✗ | return false; | |
| 109 | } | ||
| 110 | std::byte fresh[MAX_SOURCE_SPAN]; | ||
| 111 |
1/2✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 141 times.
|
141 | if (!detail::guarded_read_bytes(source_base, fresh, physical_source.size)) |
| 112 | { | ||
| 113 | ✗ | return false; | |
| 114 | } | ||
| 115 | 141 | overlay_snapshot(std::span<std::byte>{fresh, physical_source.size}, source_base, window, window_base); | |
| 116 | const detail::EnginePattern engine = detail::engine_pattern_from( | ||
| 117 | *pattern, | ||
| 118 | 282 | pattern->has_anchor() ? pattern->anchor_index() : pattern->size() | |
| 119 |
2/4✓ Branch 20 → 21 taken 141 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 141 times.
✗ Branch 23 → 55 not taken.
|
282 | ); |
| 120 | 141 | const detail::RawMatch fresh_match = detail::find_pattern_raw(fresh, match_span.size, engine); | |
| 121 |
3/4✓ Branch 25 → 26 taken 141 times.
✗ Branch 25 → 31 not taken.
✓ Branch 26 → 27 taken 137 times.
✓ Branch 26 → 31 taken 4 times.
|
141 | if (fresh_match.budget_exhausted || fresh_match.start != fresh || |
| 122 |
2/4✓ Branch 27 → 28 taken 137 times.
✗ Branch 27 → 31 not taken.
✓ Branch 28 → 29 taken 137 times.
✗ Branch 28 → 31 not taken.
|
137 | fresh_match.end != fresh + match_span.size || fresh_match.point == nullptr || |
| 123 |
2/4✓ Branch 29 → 30 taken 137 times.
✗ Branch 29 → 31 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 137 times.
|
137 | fresh_match.point < fresh || fresh_match.point > fresh + match_span.size) |
| 124 | { | ||
| 125 | 4 | return false; | |
| 126 | } | ||
| 127 | |||
| 128 | 137 | const std::size_t point_offset = static_cast<std::size_t>(fresh_match.point - fresh); | |
| 129 |
1/2✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 137 times.
|
137 | if (point_offset > static_cast<std::size_t>(UINTPTR_MAX - source_base)) |
| 130 | { | ||
| 131 | ✗ | return false; | |
| 132 | } | ||
| 133 | 137 | const std::uintptr_t live_point = source_base + point_offset; | |
| 134 | 137 | std::optional<std::uintptr_t> fresh_site; | |
| 135 |
2/2✓ Branch 35 → 36 taken 136 times.
✓ Branch 35 → 37 taken 1 time.
|
137 | if (const DirectPattern *direct = candidate.as_direct()) |
| 136 | { | ||
| 137 | 136 | fresh_site = detail::resolve_direct(live_point, *direct); | |
| 138 | } | ||
| 139 |
1/2✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 45 not taken.
|
1 | else if (const RipRelativePattern *rip = candidate.as_rip_relative()) |
| 140 | { | ||
| 141 |
1/2✓ Branch 39 → 40 taken 1 time.
✗ Branch 39 → 41 not taken.
|
1 | if (point_offset > physical_source.size || |
| 142 |
1/2✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 1 time.
|
1 | rip->instruction_length > physical_source.size - point_offset) |
| 143 | { | ||
| 144 | ✗ | return false; | |
| 145 | } | ||
| 146 | const std::span<const std::byte> instruction_snapshot{ | ||
| 147 | fresh + point_offset, | ||
| 148 | 1 | rip->instruction_length | |
| 149 | 1 | }; | |
| 150 | 1 | fresh_site = detail::resolve_rip_relative_candidate(live_point, *rip, instruction_snapshot); | |
| 151 | } | ||
| 152 |
3/4✓ Branch 46 → 47 taken 137 times.
✗ Branch 46 → 50 not taken.
✓ Branch 48 → 49 taken 135 times.
✓ Branch 48 → 50 taken 2 times.
|
137 | return fresh_site && *fresh_site == decoded_site; |
| 153 | 141 | } | |
| 154 | |||
| 155 | 154 | Result<std::int64_t> read_code_constant_impl( | |
| 156 | const CodeConstant &code_constant, | ||
| 157 | Region scope, | ||
| 158 | Region *instruction_span, | ||
| 159 | Region *physical_source | ||
| 160 | ) | ||
| 161 | { | ||
| 162 |
2/2✓ Branch 2 → 3 taken 108 times.
✓ Branch 2 → 5 taken 46 times.
|
154 | if (instruction_span != nullptr) |
| 163 | { | ||
| 164 | 108 | *instruction_span = Region{}; | |
| 165 | } | ||
| 166 |
2/2✓ Branch 5 → 6 taken 108 times.
✓ Branch 5 → 8 taken 46 times.
|
154 | if (physical_source != nullptr) |
| 167 | { | ||
| 168 | 108 | *physical_source = Region{}; | |
| 169 | } | ||
| 170 | 351 | if ((code_constant.kind != OperandKind::Immediate && | |
| 171 |
6/6✓ Branch 8 → 9 taken 43 times.
✓ Branch 8 → 10 taken 111 times.
✓ Branch 9 → 10 taken 41 times.
✓ Branch 9 → 12 taken 2 times.
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 18 taken 149 times.
|
306 | code_constant.kind != OperandKind::MemoryDisplacement) || |
| 172 |
2/2✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 149 times.
|
152 | !detail::valid_code_constant_byte_width(code_constant.byte_width)) |
| 173 | { | ||
| 174 | 5 | return std::unexpected(Error{ErrorCode::InvalidArg, "scan::read_code_constant"}); | |
| 175 | } | ||
| 176 | |||
| 177 | // Resolve the instruction site through the candidate ladder and propagate its typed failure verbatim | ||
| 178 | // (EmptyCandidates, NoMatch, InvalidRange, ...). The resolved address must name an executable | ||
| 179 | // instruction site; require_executable_result rejects an unsuitable rung and lets resolve() try a later | ||
| 180 | // ladder fallback. | ||
| 181 | // | ||
| 182 | // A code constant is encoded in machine code. Restrict byte tiers to execute-readable pages so an | ||
| 183 | // identical run in .rdata / .data cannot win or make the code match ambiguous. A RipRelative or | ||
| 184 | // walked-back candidate can still transform an executable match into a data address, so enforce the | ||
| 185 | // final-page policy in resolve() and recheck below before the fault-safe read and decode. The recheck | ||
| 186 | // narrows, but cannot eliminate, a concurrent protection change; guarded_read_bytes preserves the | ||
| 187 | // fail-closed host-safety guarantee. | ||
| 188 | 149 | const ScanRequest request{ | |
| 189 | .ladder = code_constant.site, | ||
| 190 | .label = "read_code_constant", | ||
| 191 | .scope = scope, | ||
| 192 | .pages = Pages::Executable, | ||
| 193 | .require_executable_result = true, | ||
| 194 | 149 | }; | |
| 195 |
1/2✓ Branch 20 → 21 taken 149 times.
✗ Branch 20 → 132 not taken.
|
149 | const Result<detail::ResolvedScanHit> resolved = detail::resolve_scan_with_provenance(request); |
| 196 |
2/2✓ Branch 22 → 23 taken 6 times.
✓ Branch 22 → 27 taken 143 times.
|
149 | if (!resolved) |
| 197 | { | ||
| 198 | 6 | return std::unexpected(resolved.error()); | |
| 199 | } | ||
| 200 | 143 | const std::uintptr_t site = resolved->hit.address.raw(); | |
| 201 |
2/2✓ Branch 29 → 30 taken 107 times.
✓ Branch 29 → 32 taken 36 times.
|
143 | if (physical_source != nullptr) |
| 202 | { | ||
| 203 | 107 | *physical_source = resolved->physical_source; | |
| 204 | } | ||
| 205 |
1/2✗ Branch 33 → 34 not taken.
✓ Branch 33 → 37 taken 143 times.
|
143 | if (!detail::is_executable_address(site)) |
| 206 | { | ||
| 207 | ✗ | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 208 | } | ||
| 209 | |||
| 210 | 143 | const detail::ModuleSpan range = detail::module_span(scope); | |
| 211 | |||
| 212 | // Read a full maximum-length instruction window, clamped to the module so the read never runs past the | ||
| 213 | // end of the image, behind a fault guard. A truncated window that fails to decode is reported as | ||
| 214 | // DecodeFailed below. | ||
| 215 | std::byte buf[ZYDIS_MAX_INSTRUCTION_LENGTH]; | ||
| 216 | 143 | std::size_t avail = sizeof(buf); | |
| 217 |
3/6✓ Branch 39 → 40 taken 143 times.
✗ Branch 39 → 42 not taken.
✓ Branch 40 → 41 taken 143 times.
✗ Branch 40 → 42 not taken.
✓ Branch 43 → 44 taken 143 times.
✗ Branch 43 → 46 not taken.
|
143 | if (range.valid() && site < range.end) |
| 218 | { | ||
| 219 | 143 | const std::uintptr_t to_end = range.end - site; | |
| 220 |
2/2✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 142 times.
|
143 | if (to_end < avail) |
| 221 | { | ||
| 222 | 1 | avail = static_cast<std::size_t>(to_end); | |
| 223 | } | ||
| 224 | } | ||
| 225 |
3/6✓ Branch 46 → 47 taken 143 times.
✗ Branch 46 → 49 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 143 times.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 55 taken 143 times.
|
143 | if (avail == 0 || !detail::guarded_read_bytes(site, buf, avail)) |
| 226 | { | ||
| 227 | ✗ | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 228 | } | ||
| 229 | |||
| 230 | ZydisDecoder decoder; | ||
| 231 |
2/4✓ Branch 55 → 56 taken 143 times.
✗ Branch 55 → 130 not taken.
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 60 taken 143 times.
|
143 | if (!ZYAN_SUCCESS(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64))) |
| 232 | { | ||
| 233 | ✗ | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 234 | } | ||
| 235 | |||
| 236 | ZydisDecodedInstruction insn; | ||
| 237 | ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; | ||
| 238 |
3/4✓ Branch 60 → 61 taken 143 times.
✗ Branch 60 → 130 not taken.
✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 65 taken 142 times.
|
143 | if (!ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, buf, avail, &insn, operands))) |
| 239 | { | ||
| 240 | 1 | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 241 | } | ||
| 242 |
2/2✓ Branch 66 → 67 taken 1 time.
✓ Branch 66 → 70 taken 141 times.
|
142 | if (!detail::is_executable_range(site, insn.length)) |
| 243 | { | ||
| 244 | 1 | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 245 | } | ||
| 246 | |||
| 247 |
3/6✓ Branch 72 → 73 taken 141 times.
✗ Branch 72 → 76 not taken.
✓ Branch 74 → 75 taken 141 times.
✗ Branch 74 → 76 not taken.
✓ Branch 77 → 78 taken 141 times.
✗ Branch 77 → 93 not taken.
|
141 | if (resolved->winning_index < code_constant.site.size() && resolved->match_span.size != 0) |
| 248 | { | ||
| 249 | 141 | const Candidate &winning_candidate = code_constant.site[resolved->winning_index]; | |
| 250 |
1/2✓ Branch 81 → 82 taken 141 times.
✗ Branch 81 → 88 not taken.
|
282 | if (detail::byte_pattern_of(winning_candidate) != nullptr && |
| 251 |
5/6✓ Branch 85 → 86 taken 141 times.
✗ Branch 85 → 128 not taken.
✓ Branch 86 → 87 taken 6 times.
✓ Branch 86 → 88 taken 135 times.
✓ Branch 89 → 90 taken 6 times.
✓ Branch 89 → 93 taken 135 times.
|
282 | !selector_still_resolves_site( |
| 252 | winning_candidate, | ||
| 253 | 141 | resolved->match_span, | |
| 254 | 141 | resolved->physical_source, | |
| 255 | site, | ||
| 256 | site, | ||
| 257 | 141 | std::span<const std::byte>{buf, avail} | |
| 258 | )) | ||
| 259 | { | ||
| 260 | 6 | return std::unexpected(Error{ErrorCode::EvidenceMismatch, "scan::read_code_constant"}); | |
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | // Published only once the decode has proven the length, so the provenance names the instruction's real | ||
| 265 | // extent rather than a one-byte point a co-voting selector could straddle without overlapping. | ||
| 266 |
2/2✓ Branch 93 → 94 taken 106 times.
✓ Branch 93 → 96 taken 29 times.
|
135 | if (instruction_span != nullptr) |
| 267 | { | ||
| 268 | 106 | *instruction_span = Region{Address{site}, static_cast<std::size_t>(insn.length)}; | |
| 269 | } | ||
| 270 | |||
| 271 | // Index the VISIBLE operands - the ones a human counts in a disassembler. operand_count includes | ||
| 272 | // implicit/hidden operands (flags, implicit registers, stack writes), which would make a fixed | ||
| 273 | // operand_index drift between mnemonics. | ||
| 274 |
2/2✓ Branch 96 → 97 taken 1 time.
✓ Branch 96 → 100 taken 134 times.
|
135 | if (code_constant.operand_index >= insn.operand_count_visible) |
| 275 | { | ||
| 276 | 1 | return std::unexpected(Error{ErrorCode::OperandOutOfRange, "scan::read_code_constant"}); | |
| 277 | } | ||
| 278 | 134 | const ZydisDecodedOperand &operand = operands[code_constant.operand_index]; | |
| 279 | |||
| 280 |
2/2✓ Branch 100 → 101 taken 96 times.
✓ Branch 100 → 108 taken 38 times.
|
134 | if (code_constant.kind == OperandKind::Immediate) |
| 281 | { | ||
| 282 |
2/2✓ Branch 101 → 102 taken 1 time.
✓ Branch 101 → 105 taken 95 times.
|
96 | if (operand.type != ZYDIS_OPERAND_TYPE_IMMEDIATE) |
| 283 | { | ||
| 284 | 1 | return std::unexpected(Error{ErrorCode::UnexpectedShape, "scan::read_code_constant"}); | |
| 285 | } | ||
| 286 | // imm.value.s is already 64-bit sign-extended by Zydis. | ||
| 287 | 95 | return narrow_signed(static_cast<std::int64_t>(operand.imm.value.s), code_constant.byte_width); | |
| 288 | } | ||
| 289 | |||
| 290 | // MemoryDisplacement. A register-indirect operand with no displacement (for example plain `[rcx]`) | ||
| 291 | // carries no constant to read. | ||
| 292 |
3/4✓ Branch 108 → 109 taken 38 times.
✗ Branch 108 → 110 not taken.
✓ Branch 109 → 110 taken 1 time.
✓ Branch 109 → 113 taken 37 times.
|
38 | if (operand.type != ZYDIS_OPERAND_TYPE_MEMORY || !operand.mem.disp.has_displacement) |
| 293 | { | ||
| 294 | 1 | return std::unexpected(Error{ErrorCode::UnexpectedShape, "scan::read_code_constant"}); | |
| 295 | } | ||
| 296 | |||
| 297 |
2/2✓ Branch 113 → 114 taken 11 times.
✓ Branch 113 → 122 taken 26 times.
|
37 | if (operand.mem.base == ZYDIS_REGISTER_RIP) |
| 298 | { | ||
| 299 | // RIP-relative: the raw displacement is measured from the next instruction, not the absolute | ||
| 300 | // constant the caller wants. Resolve it to the absolute target so the return value is meaningful | ||
| 301 | // rather than a misleading relative offset. | ||
| 302 | 11 | ZyanU64 absolute = 0; | |
| 303 |
2/4✓ Branch 114 → 115 taken 11 times.
✗ Branch 114 → 129 not taken.
✗ Branch 115 → 116 not taken.
✓ Branch 115 → 119 taken 11 times.
|
11 | if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&insn, &operand, static_cast<ZyanU64>(site), &absolute))) |
| 304 | { | ||
| 305 | ✗ | return std::unexpected(Error{ErrorCode::DecodeFailed, "scan::read_code_constant"}); | |
| 306 | } | ||
| 307 | 11 | return static_cast<std::int64_t>(absolute); | |
| 308 | } | ||
| 309 | |||
| 310 | // disp.value is already 64-bit sign-extended. | ||
| 311 | 26 | return narrow_signed(static_cast<std::int64_t>(operand.mem.disp.value), code_constant.byte_width); | |
| 312 | 149 | } | |
| 313 | } // namespace | ||
| 314 | |||
| 315 | 46 | Result<std::int64_t> read_code_constant(const CodeConstant &code_constant, Region scope) | |
| 316 | { | ||
| 317 | 46 | return read_code_constant_impl(code_constant, scope, nullptr, nullptr); | |
| 318 | } | ||
| 319 | } // namespace scan | ||
| 320 | |||
| 321 | Result<detail::ResolvedCodeConstant> | ||
| 322 | 108 | detail::read_code_constant_with_provenance(const scan::CodeConstant &code_constant, Region scope) | |
| 323 | { | ||
| 324 | 108 | Region instruction_span; | |
| 325 | 108 | Region physical_source; | |
| 326 | Result<std::int64_t> value = | ||
| 327 |
1/2✓ Branch 2 → 3 taken 108 times.
✗ Branch 2 → 14 not taken.
|
108 | scan::read_code_constant_impl(code_constant, scope, &instruction_span, &physical_source); |
| 328 |
2/2✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 9 taken 106 times.
|
108 | if (!value) |
| 329 | { | ||
| 330 | 2 | return std::unexpected(value.error()); | |
| 331 | } | ||
| 332 | 106 | return ResolvedCodeConstant{*value, instruction_span, physical_source}; | |
| 333 | } | ||
| 334 | } // namespace DetourModKit | ||
| 335 |