|
| 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