Skip to content

Commit 58fe09f

Browse files
committed
feat: added encoding test suite (x86 only for now)
1 parent d2364e3 commit 58fe09f

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ rd_add_test(test_memory unit/test_memory.c)
4646
add_test(NAME utils COMMAND test_utils)
4747
add_test(NAME memory COMMAND test_memory)
4848

49+
# encoding
50+
rd_add_test(test_encoding_x86 encoding/test_x86.c)
51+
52+
add_test(NAME test_encoding_x86 COMMAND test_encoding_x86)
53+
54+
set_tests_properties(test_encoding_x86 PROPERTIES DEPENDS "memory utils")
55+
4956
# integrations
5057
rd_add_test(test_tier0 integration/test_tier0.c)
5158
rd_add_test(test_tier1 integration/test_tier1.c)

encoding/test_x86.c

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include "rdtest.h"
2+
#include "rdtest_helpers.h"
3+
4+
#define RD_TEST_X86_BASE_ADDRESS 0x401000
5+
6+
static const RDProcessorPlugin* x86_32 = NULL;
7+
8+
static bool _fails(const char* text) {
9+
const char* err;
10+
return rd_encode_instruction(text, RD_TEST_X86_BASE_ADDRESS, x86_32,
11+
&err) == NULL;
12+
}
13+
14+
static bool _encode(const char* text, const u8* expected, usize n) {
15+
const RDScratchBuffer* buf =
16+
rd_encode_instruction(text, RD_TEST_X86_BASE_ADDRESS, x86_32, NULL);
17+
return buf && rd_scratch_length(buf) == n &&
18+
!memcmp(rd_scratch_data(buf), expected, n);
19+
}
20+
21+
static int test_mnemonic_only(void) {
22+
rdtest_assert(_encode("ret", (u8[]){0xC3}, 1), "ret");
23+
rdtest_assert(_encode("leave", (u8[]){0xC9}, 1), "leave");
24+
return RDTEST_PASS;
25+
}
26+
27+
static int test_reg_reg(void) {
28+
rdtest_assert(_encode("mov eax, ecx", (u8[]){0x89, 0xC8}, 2),
29+
"mov eax,ecx");
30+
return RDTEST_PASS;
31+
}
32+
33+
static int test_null_is_nop(void) {
34+
RDScratchBuffer* a = rd_scratch_create();
35+
RDScratchBuffer* b = rd_scratch_create();
36+
37+
bool ok1 = rd_encode_instruction_to(NULL, RD_TEST_X86_BASE_ADDRESS, x86_32,
38+
a, NULL);
39+
bool ok2 = rd_encode_instruction_to("nop", RD_TEST_X86_BASE_ADDRESS, x86_32,
40+
b, NULL);
41+
42+
rdtest_assert(ok1 && ok2, "both should succeed");
43+
rdtest_assert_eq(rd_scratch_length(a), rd_scratch_length(b));
44+
rdtest_assert(
45+
!memcmp(rd_scratch_data(a), rd_scratch_data(b), rd_scratch_length(a)),
46+
"NULL must equal explicit 'nop'");
47+
48+
rd_scratch_destroy(a);
49+
rd_scratch_destroy(b);
50+
return RDTEST_PASS;
51+
}
52+
53+
static int test_default_hex(void) {
54+
// x86 sets default_base=16, bare "10" means 0x10, not decimal 10
55+
rdtest_assert(_encode("add eax, 10", (u8[]){0x83, 0xC0, 0x10}, 3),
56+
"bare = hex");
57+
rdtest_assert(_encode("add eax, 0n10", (u8[]){0x83, 0xC0, 0x0A}, 3),
58+
"0n = decimal");
59+
return RDTEST_PASS;
60+
}
61+
62+
static int test_displ_plus(void) {
63+
rdtest_assert(_encode("mov [eax+4], ecx", (u8[]){0x89, 0x48, 0x04}, 3),
64+
"base+disp");
65+
return RDTEST_PASS;
66+
}
67+
68+
static int test_displ_minus(void) {
69+
// isolates the MINUS branch specifically, the inverted check rejected
70+
// both signs identically, so a passing '+' test alone wouldn't catch this
71+
rdtest_assert(_encode("mov [eax-4], ecx", (u8[]){0x89, 0x48, 0xFC}, 3),
72+
"base-disp");
73+
return RDTEST_PASS;
74+
}
75+
76+
static int test_displ_scaled_index(void) {
77+
rdtest_assert(_encode("mov [eax+ecx*4], edx", (u8[]){0x89, 0x14, 0x88}, 3),
78+
"SIB");
79+
return RDTEST_PASS;
80+
}
81+
82+
static int test_displ_no_base(void) {
83+
// the original bug report that started the size-inference discussion
84+
rdtest_assert(_fails("mov [0n1000000], eax") == false, "should encode");
85+
return RDTEST_PASS;
86+
}
87+
88+
static int test_esp_cannot_be_index(void) {
89+
rdtest_assert(_fails("mov [ecx+esp], eax"),
90+
"ESP as index must be rejected");
91+
return RDTEST_PASS;
92+
}
93+
94+
static int test_ambiguous_size_rejected(void) {
95+
rdtest_assert(_fails("mov [eax], 5"),
96+
"no register sibling, no ptr keyword");
97+
return RDTEST_PASS;
98+
}
99+
100+
static int test_explicit_size_accepted(void) {
101+
rdtest_assert(!_fails("mov dword ptr [eax], 5"),
102+
"explicit ptr resolves ambiguity");
103+
return RDTEST_PASS;
104+
}
105+
106+
static int test_unknown_mnemonic(void) {
107+
rdtest_assert(_fails("xyzzy eax, ecx"), "unknown mnemonic");
108+
return RDTEST_PASS;
109+
}
110+
111+
static int test_malformed_number(void) {
112+
rdtest_assert(_fails("mov eax, 3zzz"), "3zzz must not silently split");
113+
return RDTEST_PASS;
114+
}
115+
116+
static int test_dangling_comma(void) {
117+
rdtest_assert(_fails("mov eax, ecx,"), "trailing comma with no operand");
118+
return RDTEST_PASS;
119+
}
120+
121+
static int test_trailing_garbage(void) {
122+
rdtest_assert(_fails("mov eax, ecx foo"),
123+
"no comma, unexpected token after");
124+
return RDTEST_PASS;
125+
}
126+
127+
static const RDTest TESTS[] = {
128+
{"mnemonic_only", test_mnemonic_only},
129+
{"reg_reg", test_reg_reg},
130+
{"null_is_nop", test_null_is_nop},
131+
{"default_hex", test_default_hex},
132+
{"displ_plus", test_displ_plus},
133+
{"displ_minus", test_displ_minus},
134+
{"displ_scaled_index", test_displ_scaled_index},
135+
{"displ_no_base", test_displ_no_base},
136+
{"esp_cannot_be_index", test_esp_cannot_be_index},
137+
{"ambiguous_size_rejected", test_ambiguous_size_rejected},
138+
{"explicit_size_accepted", test_explicit_size_accepted},
139+
{"unknown_mnemonic", test_unknown_mnemonic},
140+
{"malformed_number", test_malformed_number},
141+
{"dangling_comma", test_dangling_comma},
142+
{"trailing_garbage", test_trailing_garbage},
143+
{NULL, NULL},
144+
};
145+
146+
int main(int argc, char** argv) {
147+
rdtest_init(argc, argv);
148+
149+
x86_32 = rd_processor_find("x86_32");
150+
rdtest_assert_notnull(x86_32);
151+
152+
int result = rdtest_run("x86_encode", TESTS);
153+
rdtest_deinit();
154+
155+
return result;
156+
}

0 commit comments

Comments
 (0)