102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
pcdogs.c
Go to the documentation of this file.
1#include <dttr_test.h>
2
3#include <dttr_pcdogs.h>
4
5typedef struct {
6 const char *name;
7 const uint8_t *sig;
8 const char *mask;
9 DTTR_TestFixtureMask required;
11
12typedef struct {
13 const char *name;
14 const uint8_t *sig;
15 const char *mask;
16 DTTR_TestFixtureMask required;
17 int32_t match_offset;
20 uint32_t patch_size;
22 uint32_t param_count;
25
26#include "pcdogs_blueprint_test_rows.h"
27
28#define STABLE_SIGNATURE_COUNT \
29 (sizeof(DTTR_PCDOGS_SIGNATURES) / sizeof(*DTTR_PCDOGS_SIGNATURES))
30#define STABLE_FUNCTION_COUNT \
31 (sizeof(DTTR_PCDOGS_FUNCTIONS) / sizeof(*DTTR_PCDOGS_FUNCTIONS))
32#define UNSTABLE_SIGNATURE_COUNT \
33 (sizeof(DTTR_PCDOGS_UNSTABLE_SIGNATURES) / sizeof(*DTTR_PCDOGS_UNSTABLE_SIGNATURES))
34#define UNSTABLE_FUNCTION_COUNT \
35 (sizeof(DTTR_PCDOGS_UNSTABLE_FUNCTIONS) / sizeof(*DTTR_PCDOGS_UNSTABLE_FUNCTIONS))
36
37// Skip binary-backed signature tests when fixture files are unavailable.
38static uintptr_t require_sigscan(
39 const DTTR_TestPCDOGSFixture *fixture,
40 const char *kind,
41 const char *name,
42 const uint8_t *sig,
43 const char *mask,
44 const DTTR_TestPEImage *image
45) {
46 const uintptr_t rva = pcdogs_sigscan(image, sig, mask);
48 const size_t matches = pcdogs_sigscan_count(image, sig, mask);
49 if (matches == 1) {
50 return rva;
51 }
52
53 fail_msg(
54 "required %s %s resolved %zu times in %s (%s); expected exactly one match",
55 kind,
56 name,
57 matches,
58 fixture->id,
59 fixture->filename
60 );
61 return rva;
62 }
63
64 fail_msg(
65 "required %s %s did not resolve in %s (%s)",
66 kind,
67 name,
68 fixture->id,
69 fixture->filename
70 );
72}
73
74// Decode one instruction at an expected fixture address for patch-window checks.
76 const DTTR_TestPCDOGSFixture *fixture,
77 const char *kind,
78 const char *name,
79 const DTTR_TestPEImage *image,
80 uintptr_t rva
81) {
82 DTTR_TestDecodedInstruction decoded = {0};
83 if (dttr_test_zydis_decode32_at(image, rva, &decoded)) {
84 assert_true(decoded.instruction.length > 0);
85 return;
86 }
87
88 fail_msg(
89 "required %s %s did not decode in %s at 0x%08X",
90 kind,
91 name,
92 fixture->id,
93 (unsigned)rva
94 );
95}
96
97// Verify a generated signature resolves in every required fixture image.
99 const DTTR_TestPCDOGSFixture *fixture,
100 const blueprint_signature *sig,
101 const DTTR_TestPEImage *image
102) {
103 const uintptr_t rva = require_sigscan(
104 fixture,
105 "signature",
106 sig->name,
107 sig->sig,
108 sig->mask,
109 image
110 );
111 assert_decodes_at(fixture, "signature", sig->name, image, rva);
112}
113
114// Run generated signature expectations against one loaded PCDOGS fixture.
116 size_t fixture_index,
117 const DTTR_TestBinaryFixture *fixture,
118 const char *path,
119 const DTTR_TestPEImage *image,
120 void *userdata
121) {
122 for (size_t sig_index = 0; sig_index < STABLE_SIGNATURE_COUNT; sig_index++) {
123 const blueprint_signature *sig = &DTTR_PCDOGS_SIGNATURES[sig_index];
124 if (!dttr_test_fixture_required(sig->required, fixture_index)) {
125 continue;
126 }
127
128 assert_signature_resolved(fixture, sig, image);
129 }
130
131 for (size_t sig_index = 0; sig_index < UNSTABLE_SIGNATURE_COUNT; sig_index++) {
132 const blueprint_signature *sig = &DTTR_PCDOGS_UNSTABLE_SIGNATURES[sig_index];
133 if (!dttr_test_fixture_required(sig->required, fixture_index)) {
134 continue;
135 }
136
137 assert_signature_resolved(fixture, sig, image);
138 }
139
140 return true;
141}
142
143// Covers generated signature rows resolving across required PCDOGS fixtures.
148
149// Return the matched function address adjusted by its blueprint match offset.
150static uintptr_t blueprint_function_site(
151 const DTTR_TestPCDOGSFixture *fixture,
152 const blueprint_function *fn,
153 const DTTR_TestPEImage *image
154) {
155 const uintptr_t match = require_sigscan(
156 fixture,
157 "blueprint function",
158 fn->name,
159 fn->sig,
160 fn->mask,
161 image
162 );
163 if (!dttr_test_signed_range_valid(match, fn->match_offset, 1u, image->image_size)) {
164 fail_msg(
165 "blueprint function %s match offset outside %s: match=0x%08X offset=%d",
166 fn->name,
167 fixture->id,
168 (unsigned)match,
169 (int)fn->match_offset
170 );
171 }
172
173 return dttr_test_offset_site(match, fn->match_offset);
174}
175
176// Find prologue instructions that the trampoline relocator cannot support.
177static bool instruction_has_unsupported_reloc(const DTTR_TestDecodedInstruction *decoded) {
178 for (size_t imm_idx = 0; imm_idx < 2; imm_idx++) {
179 if (!decoded->instruction.raw.imm[imm_idx].size
180 || !decoded->instruction.raw.imm[imm_idx].is_relative) {
181 continue;
182 }
183
184 const uint8_t rel_size = (uint8_t)(decoded->instruction.raw.imm[imm_idx].size
185 / 8u);
186 if (rel_size != 4u) {
187 return true;
188 }
189
190 if ((size_t)decoded->instruction.raw.imm[imm_idx].offset + rel_size
191 > decoded->instruction.length) {
192 return true;
193 }
194 }
195
196 return false;
197}
198
199// Verify generated hook patch windows contain decodable and supported instructions.
201 const DTTR_TestPCDOGSFixture *fixture,
202 const blueprint_function *fn,
203 const DTTR_TestPEImage *image,
204 uintptr_t site,
205 uint32_t patch_size
206) {
207 if (patch_size == 0u) {
208 return;
209 }
210
211 if (!dttr_test_range_valid((size_t)site, patch_size, image->image_size)) {
212 fail_msg(
213 "blueprint hook %s patch window outside %s at 0x%08X size=%u",
214 fn->name,
215 fixture->id,
216 (unsigned)site,
217 (unsigned)patch_size
218 );
219 }
220
221 size_t decoded_size = 0;
222 while (decoded_size < patch_size) {
223 DTTR_TestDecodedInstruction decoded = {0};
224 assert_true(dttr_test_zydis_decode32_at(image, site + decoded_size, &decoded));
225 assert_true(decoded.instruction.length > 0);
226 if (instruction_has_unsupported_reloc(&decoded)) {
227 fail_msg(
228 "blueprint hook %s has unsupported relative immediate in %s at 0x%08X",
229 fn->name,
230 fixture->id,
231 (unsigned)(site + decoded_size)
232 );
233 }
234
235 decoded_size += decoded.instruction.length;
236 }
237}
238
239// Read stdcall stack cleanup bytes from a fixture RET instruction.
240static uint32_t ret_stack_bytes(const DTTR_TestDecodedInstruction *decoded) {
241 if (decoded->instruction.raw.imm[0].size) {
242 return (uint32_t)decoded->instruction.raw.imm[0].value.u;
243 }
244
245 return 0u;
246}
247
248// Compare recovered ABI stack cleanup against generated blueprint metadata.
250 const DTTR_TestPCDOGSFixture *fixture,
251 const blueprint_function *fn,
252 const DTTR_TestPEImage *image,
253 uintptr_t site
254) {
255 const uint32_t expected = fn->calling_convention == DTTR_PCDOGS_CC_CDECL
256 ? 0u
257 : fn->stack_param_bytes;
258 uintptr_t rva = site;
259 for (size_t decoded_count = 0; decoded_count < 2048u; decoded_count++) {
260 DTTR_TestDecodedInstruction decoded = {0};
261 if (!dttr_test_zydis_decode32_at(image, rva, &decoded)) {
262 fail_msg(
263 "blueprint function %s decode failed in %s while scanning for ABI RET "
264 "at "
265 "0x%08X",
266 fn->name,
267 fixture->id,
268 (unsigned)rva
269 );
270 }
271
272 assert_true(decoded.instruction.length > 0);
273 if (decoded.instruction.mnemonic == ZYDIS_MNEMONIC_JMP) {
274 return;
275 }
276
277 if (decoded.instruction.mnemonic == ZYDIS_MNEMONIC_RET) {
278 const uint32_t actual = ret_stack_bytes(&decoded);
279 if (actual != expected) {
280 fail_msg(
281 "blueprint function %s ABI cleanup mismatch in %s at 0x%08X: "
282 "expected %u got %u",
283 fn->name,
284 fixture->id,
285 (unsigned)rva,
286 (unsigned)expected,
287 (unsigned)actual
288 );
289 }
290
291 return;
292 }
293
294 rva += decoded.instruction.length;
295 if (rva >= image->image_size) {
296 break;
297 }
298 }
299
300 fail_msg("blueprint function %s has no decoded RET in %s", fn->name, fixture->id);
301}
302
303// Verify one generated function row resolves and matches hook/ABI expectations.
305 const DTTR_TestPCDOGSFixture *fixture,
306 const blueprint_function *fn,
307 const DTTR_TestPEImage *image
308) {
309 const uintptr_t site = blueprint_function_site(fixture, fn, image);
310 assert_decodes_at(fixture, "blueprint function", fn->name, image, site);
311 assert_abi_return_matches(fixture, fn, image, site);
313 assert_patch_window_decodes(fixture, fn, image, site, fn->patch_size);
314 } else if (fn->hook_kind == DTTR_PCDOGS_HOOK_HOTPATCH) {
315 if (site < 5u) {
316 fail_msg("blueprint hotpatch function %s has no pre-entry slot", fn->name);
317 }
318
319 assert_patch_window_decodes(fixture, fn, image, site - 5u, 5u);
320 assert_patch_window_decodes(fixture, fn, image, site, fn->entry_patch_size);
321 }
322}
323
324// Run generated function expectations against one loaded PCDOGS fixture.
326 size_t fixture_index,
327 const DTTR_TestBinaryFixture *fixture,
328 const char *path,
329 const DTTR_TestPEImage *image,
330 void *userdata
331) {
332 for (size_t fn_index = 0; fn_index < STABLE_FUNCTION_COUNT; fn_index++) {
333 const blueprint_function *fn = &DTTR_PCDOGS_FUNCTIONS[fn_index];
334 if (dttr_test_fixture_required(fn->required, fixture_index)) {
335 assert_blueprint_function_resolved(fixture, fn, image);
336 }
337 }
338
339 for (size_t fn_index = 0; fn_index < UNSTABLE_FUNCTION_COUNT; fn_index++) {
340 const blueprint_function *fn = &DTTR_PCDOGS_UNSTABLE_FUNCTIONS[fn_index];
341 if (dttr_test_fixture_required(fn->required, fixture_index)) {
342 assert_blueprint_function_resolved(fixture, fn, image);
343 }
344 }
345
346 return true;
347}
348
349// Covers generated function rows resolving with matching hook windows and ABI data.
354
355static const DTTR_TestCase TEST_CASES[] = {
357 {"blueprint-functions", test_blueprint_functions_resolve_and_match_abi},
358};
359
bool dttr_test_signed_range_valid(uintptr_t base, int32_t offset, size_t size, size_t total)
Definition binary.c:134
bool dttr_test_fixture_required(DTTR_TestFixtureMask required, size_t fixture_index)
Definition binary.c:187
bool dttr_test_zydis_decode32_at(const DTTR_TestPEImage *image, uintptr_t rva, DTTR_TestDecodedInstruction *out)
Definition binary.c:675
uintptr_t dttr_test_offset_site(uintptr_t base, int32_t offset)
Definition binary.c:159
bool dttr_test_range_valid(size_t offset, size_t size, size_t total)
Definition binary.c:125
const DTTR_BackendState * state
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
static const DTTR_TestCase TEST_CASES[]
Definition core.c:17
DTTR_PCDOGS_T_Calling_Convention
@ DTTR_PCDOGS_CC_CDECL
DTTR_PCDOGS_T_Hook_Kind
@ DTTR_PCDOGS_HOOK_REL32
@ DTTR_PCDOGS_HOOK_HOTPATCH
static uintptr_t pcdogs_sigscan(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Scan a fixture image with the same signature format used by SDK symbol rows.
Definition dttr_test.h:56
DTTR_TestBinaryFixture DTTR_TestPCDOGSFixture
Definition dttr_test.h:37
bool pcdogs_fixtures_available()
Report whether all PCDOGS binary fixtures are available for signature tests.
bool pcdogs_for_each_fixture(DTTR_TestPEFixtureVisitor visitor, void *userdata)
Visit each available PCDOGS fixture image for signature and ABI checks.
static size_t pcdogs_sigscan_count(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Count signature matches in a fixture image.
Definition dttr_test.h:65
#define DTTR_TEST_PCDOGS_SIG_NOT_FOUND
Definition dttr_test.h:20
static void dttr_test_require_available(bool available)
#define DTTR_TEST_MAIN(TESTS)
#define STABLE_FUNCTION_COUNT
Definition pcdogs.c:30
static void assert_blueprint_function_resolved(const DTTR_TestPCDOGSFixture *fixture, const blueprint_function *fn, const DTTR_TestPEImage *image)
Definition pcdogs.c:304
static void test_blueprint_functions_resolve_and_match_abi(void **state)
Definition pcdogs.c:350
#define UNSTABLE_FUNCTION_COUNT
Definition pcdogs.c:34
static bool instruction_has_unsupported_reloc(const DTTR_TestDecodedInstruction *decoded)
Definition pcdogs.c:177
static uint32_t ret_stack_bytes(const DTTR_TestDecodedInstruction *decoded)
Definition pcdogs.c:240
static void assert_patch_window_decodes(const DTTR_TestPCDOGSFixture *fixture, const blueprint_function *fn, const DTTR_TestPEImage *image, uintptr_t site, uint32_t patch_size)
Definition pcdogs.c:200
static bool assert_signatures_for_fixture(size_t fixture_index, const DTTR_TestBinaryFixture *fixture, const char *path, const DTTR_TestPEImage *image, void *userdata)
Definition pcdogs.c:115
#define STABLE_SIGNATURE_COUNT
Definition pcdogs.c:28
static bool assert_blueprint_functions_for_fixture(size_t fixture_index, const DTTR_TestBinaryFixture *fixture, const char *path, const DTTR_TestPEImage *image, void *userdata)
Definition pcdogs.c:325
static void assert_decodes_at(const DTTR_TestPCDOGSFixture *fixture, const char *kind, const char *name, const DTTR_TestPEImage *image, uintptr_t rva)
Definition pcdogs.c:75
#define UNSTABLE_SIGNATURE_COUNT
Definition pcdogs.c:32
static void assert_signature_resolved(const DTTR_TestPCDOGSFixture *fixture, const blueprint_signature *sig, const DTTR_TestPEImage *image)
Definition pcdogs.c:98
static uintptr_t require_sigscan(const DTTR_TestPCDOGSFixture *fixture, const char *kind, const char *name, const uint8_t *sig, const char *mask, const DTTR_TestPEImage *image)
Definition pcdogs.c:38
static void test_expected_pcdogs_signatures_resolve(void **state)
Definition pcdogs.c:144
static void assert_abi_return_matches(const DTTR_TestPCDOGSFixture *fixture, const blueprint_function *fn, const DTTR_TestPEImage *image, uintptr_t site)
Definition pcdogs.c:249
static uintptr_t blueprint_function_site(const DTTR_TestPCDOGSFixture *fixture, const blueprint_function *fn, const DTTR_TestPEImage *image)
Definition pcdogs.c:150
DTTR_PCDOGS_T_Hook_Kind hook_kind
Definition pcdogs.c:19
uint32_t stack_param_bytes
Definition pcdogs.c:23
DTTR_TestFixtureMask required
Definition pcdogs.c:16
uint32_t param_count
Definition pcdogs.c:22
uint32_t patch_size
Definition pcdogs.c:20
const char * mask
Definition pcdogs.c:15
const uint8_t * sig
Definition pcdogs.c:14
int32_t match_offset
Definition pcdogs.c:17
const char * name
Definition pcdogs.c:13
uint32_t entry_patch_size
Definition pcdogs.c:21
DTTR_PCDOGS_T_Calling_Convention calling_convention
Definition pcdogs.c:18
DTTR_TestFixtureMask required
Definition pcdogs.c:9
const char * mask
Definition pcdogs.c:8
const char * name
Definition pcdogs.c:6
const uint8_t * sig
Definition pcdogs.c:7