102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
dttr_test_support.h
Go to the documentation of this file.
1#ifndef DTTR_TEST_SUPPORT_H
2#define DTTR_TEST_SUPPORT_H
3
4#include <setjmp.h>
5#include <stdarg.h>
6#include <stdbool.h>
7#include <stddef.h>
8
9#include <cmocka.h>
10
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14
15typedef struct {
16 const char *name;
17 CMUnitTestFunction fn;
19
20#define DTTR_TEST_ARRAY_COUNT(TESTS) (sizeof(TESTS) / sizeof(*(TESTS)))
21#define DTTR_TEST_MAIN(TESTS) \
22 int main(int argc, char **argv) { \
23 return dttr_test_run_cases((TESTS), DTTR_TEST_ARRAY_COUNT(TESTS), argc, argv); \
24 }
25
26// Runs one named cmocka case for focused local debugging.
27static inline int dttr_test_run_case(const DTTR_TestCase *test_case) {
28 if (!test_case) {
29 return 2;
30 }
31
32 const struct CMUnitTest tests[] = {
33 cmocka_unit_test(test_case->fn),
34 };
35
36 return cmocka_run_group_tests_name(test_case->name, tests, NULL, NULL);
37}
38
39// Skips fixture-dependent tests when the external binary corpus is not present.
40static inline void dttr_test_require_available(bool available) {
41 if (available) {
42 return;
43 }
44
45 const char *required = getenv("DTTR_REQUIRE_PCDOGS_FIXTURES");
46 if (required && required[0] && strcmp(required, "0") != 0) {
47 fail_msg("required PCDOGS fixtures are unavailable");
48 }
49
50 skip();
51}
52
53// Looks up the optional command-line test name used for focused local debugging.
55 const DTTR_TestCase *test_cases,
56 size_t test_case_count,
57 const char *name
58) {
59 if (!test_cases || !name) {
60 return NULL;
61 }
62
63 for (size_t i = 0; i < test_case_count; i++) {
64 if (strcmp(name, test_cases[i].name) == 0) {
65 return &test_cases[i];
66 }
67 }
68
69 return NULL;
70}
71
72// Dispatches either the whole suite or one named case when requested directly.
73static inline int dttr_test_run_cases(
74 const DTTR_TestCase *test_cases,
75 size_t test_case_count,
76 int argc,
77 char **argv
78) {
79 if (!test_cases || !argv || argc < 1) {
80 return 2;
81 }
82
83 if (argc == 2) {
84 const DTTR_TestCase *test_case = dttr_test_find_case(
85 test_cases,
86 test_case_count,
87 argv[1]
88 );
89 if (test_case) {
90 return dttr_test_run_case(test_case);
91 }
92
93 fprintf(stderr, "unknown test case: %s\n", argv[1]);
94 return 2;
95 }
96
97 if (argc != 1) {
98 fprintf(stderr, "usage: %s [test-case]\n", argv[0]);
99 return 2;
100 }
101
102 int status = 0;
103 for (size_t i = 0; i < test_case_count; i++) {
104 const int test_status = dttr_test_run_case(&test_cases[i]);
105 if (test_status != 0) {
106 status = test_status;
107 }
108 }
109
110 return status;
111}
112
113#endif // DTTR_TEST_SUPPORT_H
114
115#if defined(DTTR_TEST_BINARY_SUPPORT) && !defined(DTTR_TEST_BINARY_SUPPORT_H)
116#define DTTR_TEST_BINARY_SUPPORT_H
117
118#include <stdint.h>
119
120#include <windows.h>
121
122#include <Zydis/Zydis.h>
123#include <sds.h>
124
125#define DTTR_TEST_SIG_NOT_FOUND UINTPTR_MAX
126#define DTTR_TEST_IMPORT_CAP 128u
127#define DTTR_TEST_FIXTURE_BIT(INDEX) (UINT64_C(1) << (INDEX))
128#define DTTR_TEST_FIXTURE_MASK_ALL(COUNT) \
129 ((DTTR_TestFixtureMask)(DTTR_TEST_FIXTURE_BIT(COUNT) - UINT64_C(1)))
130
131typedef uint64_t DTTR_TestFixtureMask;
132
133typedef struct {
134 const char *id;
135 const char *filename;
136 size_t size;
137 uint64_t xxh3;
138} DTTR_TestBinaryFixture;
139
140typedef struct {
141 uint8_t *file;
142 size_t file_size;
143 uint8_t *image;
144 size_t image_size;
145 IMAGE_DATA_DIRECTORY imports_dir;
146} DTTR_TestPEImage;
147
148typedef struct {
149 const char *name;
150 const uint8_t *sig;
151 const char *mask;
152 DTTR_TestFixtureMask required;
153} DTTR_TestPatternExpectation;
154
155typedef struct {
156 ZydisDecodedInstruction instruction;
157 ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
158} DTTR_TestDecodedInstruction;
159
160typedef struct {
161 const char *dll;
162 const char *name;
163 uintptr_t iat_site;
164} DTTR_TestImportEntry;
165
166typedef enum {
167 DTTR_TEST_TARGET_RESOLVE,
168 DTTR_TEST_TARGET_JMP_HOOK,
169 DTTR_TEST_TARGET_TRAMPOLINE_HOOK,
170 DTTR_TEST_TARGET_POINTER_FF25_E8_TARGET,
171 DTTR_TEST_TARGET_POINTER_U32_AT_MATCH_PLUS_2,
172 DTTR_TEST_TARGET_BYTE_PATCH,
173} DTTR_TestTargetKind;
174
175typedef struct {
176 const char *name;
177 DTTR_TestTargetKind kind;
178 const uint8_t *sig;
179 const char *mask;
180 DTTR_TestFixtureMask required;
181 int32_t site_offset;
182 const uint8_t *patch_bytes;
183 size_t patch_size;
184 const uint8_t *expected_original;
185 const char *expected_original_mask;
186} DTTR_TestTargetExpectation;
187
188typedef bool (*DTTR_TestPEFixtureVisitor)(
189 size_t fixture_index,
190 const DTTR_TestBinaryFixture *fixture,
191 const char *path,
192 const DTTR_TestPEImage *image,
193 void *userdata
194);
195
196// Validates PE offsets before test helpers read raw fixture or mapped image buffers.
197bool dttr_test_range_valid(size_t offset, size_t size, size_t total);
198// Applies hook-site relative offsets safely before checking bytes in a PE image.
200 uintptr_t base,
201 int32_t offset,
202 size_t size,
203 size_t total
204);
205// Computes the concrete PE site address for a signed target offset.
206uintptr_t dttr_test_offset_site(uintptr_t base, int32_t offset);
207// Compares test case names with the fixture path UTF-8 rules.
208bool dttr_test_case_equal(const char *a, const char *b);
209// Tests whether a target expectation applies to the current binary fixture index.
210bool dttr_test_fixture_required(DTTR_TestFixtureMask required, size_t fixture_index);
211
212// Checks fixture availability before fixture-dependent tests decide whether to run or
213// skip.
215 const DTTR_TestBinaryFixture *fixtures,
216 size_t fixture_count,
217 const char *fixture_dir
218);
219// Opens each declared PE fixture and passes the mapped image to a test visitor.
221 const DTTR_TestBinaryFixture *fixtures,
222 size_t fixture_count,
223 const char *fixture_dir,
224 DTTR_TestPEFixtureVisitor visitor,
225 void *userdata
226);
227// Counts masked signature matches in a mapped PE fixture image.
229 const DTTR_TestPEImage *image,
230 const uint8_t *sig,
231 const char *mask
232);
233// Searches a mapped PE fixture image for a signature RVA.
234uintptr_t DTTR_TestPE_Sigscan(
235 const DTTR_TestPEImage *image,
236 const uint8_t *sig,
237 const char *mask
238);
239// Collects import names and IAT RVAs so hook tests can verify imported targets.
241 const DTTR_TestPEImage *image,
242 DTTR_TestImportEntry *imports,
243 size_t imports_cap
244);
245
246// Decodes a 32-bit instruction at a mapped PE RVA for hook-site checks.
248 const DTTR_TestPEImage *image,
249 uintptr_t rva,
250 DTTR_TestDecodedInstruction *out
251);
252// Verifies that a fixture signature satisfies the expected hook kind.
254 const DTTR_TestBinaryFixture *fixture,
255 const DTTR_TestTargetExpectation *target,
256 const DTTR_TestPEImage *image
257);
258
259#endif // DTTR_TEST_BINARY_SUPPORT_H
void dttr_test_assert_target_resolved(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image)
Definition binary.c:842
bool dttr_test_case_equal(const char *a, const char *b)
Definition binary.c:183
bool dttr_test_pe_for_each_fixture(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir, DTTR_TestPEFixtureVisitor visitor, void *userdata)
Definition binary.c:405
bool dttr_test_fixtures_available(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir)
Definition binary.c:191
size_t DTTR_TestPE_CollectImports(const DTTR_TestPEImage *image, DTTR_TestImportEntry *imports, size_t imports_cap)
Definition binary.c:567
uintptr_t DTTR_TestPE_Sigscan(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Definition binary.c:524
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
size_t DTTR_TestPE_SigscanCount(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Definition binary.c:512
void * id
const DWORD size
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Graphics_COM_DirectDrawSurface7 DWORD DWORD void void DWORD flags DTTR_Graphics_COM_DirectDrawSurface7 void void *cb void * target
static int dttr_test_run_case(const DTTR_TestCase *test_case)
static int dttr_test_run_cases(const DTTR_TestCase *test_cases, size_t test_case_count, int argc, char **argv)
static const DTTR_TestCase * dttr_test_find_case(const DTTR_TestCase *test_cases, size_t test_case_count, const char *name)
static void dttr_test_require_available(bool available)
const char * name
CMUnitTestFunction fn