GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 74.7% 74 / 0 / 99
Functions: 100.0% 6 / 0 / 6
Branches: 60.6% 20 / 0 / 33

src/hook_mid_context.cpp
Line Branch Exec Source
1 /**
2 * @file hook_mid_context.cpp
3 * @brief This TU implements the hook::MidContext accessor bridge over the backend register frame.
4 */
5
6 #include "DetourModKit/hook.hpp"
7
8 #include "internal/hook_backend.hpp"
9
10 #include <cstddef>
11 #include <cstdint>
12 #include <cstring>
13
14 namespace DetourModKit
15 {
16 namespace hook
17 {
18 // The hook::MidContext accessor bridge keeps MidContext incomplete. These accessors alone recover the real
19 // safetyhook::Context64 through reinterpret_cast. The reference always denotes the exact Context64 from the
20 // backend, so the cast is well-defined.
21 19 std::uintptr_t &gpr(MidContext &ctx, Gpr reg) noexcept
22 {
23 19 auto &context = reinterpret_cast<safetyhook::Context64 &>(ctx);
24
3/16
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✓ Branch 2 → 5 taken 13 times.
✓ Branch 2 → 6 taken 4 times.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 9 not taken.
✓ Branch 2 → 10 taken 2 times.
✗ Branch 2 → 11 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 2 → 13 not taken.
✗ Branch 2 → 14 not taken.
✗ Branch 2 → 15 not taken.
✗ Branch 2 → 16 not taken.
✗ Branch 2 → 17 not taken.
✗ Branch 2 → 18 not taken.
19 switch (reg)
25 {
26 case Gpr::Rax:
27 return context.rax;
28 case Gpr::Rbx:
29 return context.rbx;
30 13 case Gpr::Rcx:
31 13 return context.rcx;
32 4 case Gpr::Rdx:
33 4 return context.rdx;
34 case Gpr::Rsi:
35 return context.rsi;
36 case Gpr::Rdi:
37 return context.rdi;
38 case Gpr::Rbp:
39 return context.rbp;
40 2 case Gpr::R8:
41 2 return context.r8;
42 case Gpr::R9:
43 return context.r9;
44 case Gpr::R10:
45 return context.r10;
46 case Gpr::R11:
47 return context.r11;
48 case Gpr::R12:
49 return context.r12;
50 case Gpr::R13:
51 return context.r13;
52 case Gpr::R14:
53 return context.r14;
54 case Gpr::R15:
55 return context.r15;
56 }
57 // Every enumerator returns above. The rax return keeps the function well-formed.
58 return context.rax;
59 }
60
61 1 std::uintptr_t stack_pointer(const MidContext &ctx) noexcept
62 {
63 1 return reinterpret_cast<const safetyhook::Context64 &>(ctx).rsp;
64 }
65
66 1 std::uintptr_t &resume_stack_pointer(MidContext &ctx) noexcept
67 {
68 1 return reinterpret_cast<safetyhook::Context64 &>(ctx).trampoline_rsp;
69 }
70
71 1 std::uintptr_t &instruction_pointer(MidContext &ctx) noexcept
72 {
73 1 return reinterpret_cast<safetyhook::Context64 &>(ctx).rip;
74 }
75
76 3 std::uintptr_t &flags(MidContext &ctx) noexcept
77 {
78 3 return reinterpret_cast<safetyhook::Context64 &>(ctx).rflags;
79 }
80
81 // The mid-hook assembly stub stores each captured register at a fixed offset. These assertions pin the C++
82 // layout to the complete frame ABI, with the XMM prefix and integer/resume tail (T-XMM). Pointer arithmetic
83 // from &xmm0 across distinct members is undefined C++. Layout assertions do not legalize it, so the switch
84 // below uses explicit member selection.
85 static_assert(sizeof(safetyhook::Xmm) == 16);
86 static_assert(offsetof(safetyhook::Context64, xmm0) == 0);
87 static_assert(offsetof(safetyhook::Context64, xmm1) == 16);
88 static_assert(offsetof(safetyhook::Context64, xmm2) == 32);
89 static_assert(offsetof(safetyhook::Context64, xmm3) == 48);
90 static_assert(offsetof(safetyhook::Context64, xmm4) == 64);
91 static_assert(offsetof(safetyhook::Context64, xmm5) == 80);
92 static_assert(offsetof(safetyhook::Context64, xmm6) == 96);
93 static_assert(offsetof(safetyhook::Context64, xmm7) == 112);
94 static_assert(offsetof(safetyhook::Context64, xmm8) == 128);
95 static_assert(offsetof(safetyhook::Context64, xmm9) == 144);
96 static_assert(offsetof(safetyhook::Context64, xmm10) == 160);
97 static_assert(offsetof(safetyhook::Context64, xmm11) == 176);
98 static_assert(offsetof(safetyhook::Context64, xmm12) == 192);
99 static_assert(offsetof(safetyhook::Context64, xmm13) == 208);
100 static_assert(offsetof(safetyhook::Context64, xmm14) == 224);
101 static_assert(offsetof(safetyhook::Context64, xmm15) == 240);
102 static_assert(offsetof(safetyhook::Context64, rflags) == 256);
103 static_assert(offsetof(safetyhook::Context64, r15) == 264);
104 static_assert(offsetof(safetyhook::Context64, r14) == 272);
105 static_assert(offsetof(safetyhook::Context64, r13) == 280);
106 static_assert(offsetof(safetyhook::Context64, r12) == 288);
107 static_assert(offsetof(safetyhook::Context64, r11) == 296);
108 static_assert(offsetof(safetyhook::Context64, r10) == 304);
109 static_assert(offsetof(safetyhook::Context64, r9) == 312);
110 static_assert(offsetof(safetyhook::Context64, r8) == 320);
111 static_assert(offsetof(safetyhook::Context64, rdi) == 328);
112 static_assert(offsetof(safetyhook::Context64, rsi) == 336);
113 static_assert(offsetof(safetyhook::Context64, rdx) == 344);
114 static_assert(offsetof(safetyhook::Context64, rcx) == 352);
115 static_assert(offsetof(safetyhook::Context64, rbx) == 360);
116 static_assert(offsetof(safetyhook::Context64, rax) == 368);
117 static_assert(offsetof(safetyhook::Context64, rbp) == 376);
118 static_assert(offsetof(safetyhook::Context64, rsp) == 384);
119 static_assert(offsetof(safetyhook::Context64, trampoline_rsp) == 392);
120 static_assert(offsetof(safetyhook::Context64, rip) == 400);
121 static_assert(sizeof(safetyhook::Context64) == 408);
122
123 20 XmmView xmm(const MidContext &ctx, std::size_t index) noexcept
124 {
125 20 XmmView view{};
126 20 const auto &context = reinterpret_cast<const safetyhook::Context64 &>(ctx);
127 20 const safetyhook::Xmm *reg = nullptr;
128
17/17
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 2 → 7 taken 1 time.
✓ Branch 2 → 8 taken 1 time.
✓ Branch 2 → 9 taken 1 time.
✓ Branch 2 → 10 taken 1 time.
✓ Branch 2 → 11 taken 1 time.
✓ Branch 2 → 12 taken 1 time.
✓ Branch 2 → 13 taken 1 time.
✓ Branch 2 → 14 taken 1 time.
✓ Branch 2 → 15 taken 1 time.
✓ Branch 2 → 16 taken 1 time.
✓ Branch 2 → 17 taken 1 time.
✓ Branch 2 → 18 taken 1 time.
✓ Branch 2 → 19 taken 3 times.
20 switch (index)
129 {
130 2 case 0:
131 2 reg = &context.xmm0;
132 2 break;
133 1 case 1:
134 1 reg = &context.xmm1;
135 1 break;
136 1 case 2:
137 1 reg = &context.xmm2;
138 1 break;
139 1 case 3:
140 1 reg = &context.xmm3;
141 1 break;
142 1 case 4:
143 1 reg = &context.xmm4;
144 1 break;
145 1 case 5:
146 1 reg = &context.xmm5;
147 1 break;
148 1 case 6:
149 1 reg = &context.xmm6;
150 1 break;
151 1 case 7:
152 1 reg = &context.xmm7;
153 1 break;
154 1 case 8:
155 1 reg = &context.xmm8;
156 1 break;
157 1 case 9:
158 1 reg = &context.xmm9;
159 1 break;
160 1 case 10:
161 1 reg = &context.xmm10;
162 1 break;
163 1 case 11:
164 1 reg = &context.xmm11;
165 1 break;
166 1 case 12:
167 1 reg = &context.xmm12;
168 1 break;
169 1 case 13:
170 1 reg = &context.xmm13;
171 1 break;
172 1 case 14:
173 1 reg = &context.xmm14;
174 1 break;
175 1 case 15:
176 1 reg = &context.xmm15;
177 1 break;
178 3 default:
179 // Fail closed: an out-of-range index returns the zeroed view.
180 3 return view;
181 }
182 // The 16 bytes are copied out by value: XMM is surfaced read-only.
183 17 std::memcpy(view.bytes.data(), reg->u8, view.bytes.size());
184 17 return view;
185 }
186 } // namespace hook
187 } // namespace DetourModKit
188