diff --git a/x64_funtime.cpp b/x64_funtime.cpp new file mode 100644 index 0000000..b926cdf --- /dev/null +++ b/x64_funtime.cpp @@ -0,0 +1,190 @@ +#include +#include +#include + +#define assert(x) do{if(!(x))__debugbreak();}while(0) +#define assert_in_range(x, a, b) assert(((x) >= a) && ((x) <= b)) +#define assert_is_one(x) assert_in_range(x, 0, 1) + +uint8_t* code_start; +uint8_t* code; + +void emit(uint8_t c) { + *code++ = c; +} + +void emit32(uint32_t c) { + *(uint32_t*)code = c; + code += 4; +} + +// Prefix ordering in X64: +// 1. Legacy prefixes (optional) +// 2. REX prefix (optional) +// 3. Opcode (1 or 2 or 3 bytes) +// 4. ModR/M (1 byte if required) +// 5. SIB (1 byte if required) +// 6. Displacement (1 or 2 or 4 bytes) +// 7. Immediate (1 or 2 or 4 bytes) + +// Basic op codes +const uint8_t OP_BREAK = 0xCC; +const uint8_t OP_RETURN = 0xC3; + +// From manual: +// The operand-size override prefix allows a program to switch between 16-and 32-bit operand sizes. Either size can +// be the default; use of the prefix selects the non-default size. +// When using the REX prefix, adding this changes the operand size to 32bits +const uint8_t OPERAND_SIZE_OVERRIDE = 0x66; +const uint8_t GS_SEGMENT_OVERRIDE = 0x65; + +// Registers +// al(8lo), ah(8hi), ax(16), eax(32), rax(64) +enum{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15}; +typedef uint8_t Register; + +// REX - 64 bit mode prefix +// we write this when +// * instruction references extended(64bit) register +// * uses a 64 bit operand. +// W 1 bit When 1, a 64 - bit operand size is used. Otherwise, when 0, the default operand size is used(which is 32 - bit for most but not all instructions, see this table). +// R 1 bit This 1 - bit value is an extension to the MODRM.reg field.See Registers. +// X 1 bit This 1 - bit value is an extension to the SIB.index field.See 64 - bit addressing. +// B 1 bit This 1 - bit value is an extension to the MODRM.rm field or the SIB.base field.See 64 - bit addressing. +uint8_t rex(uint8_t w, uint8_t r, uint8_t x, uint8_t b) { + assert_is_one(w); assert_is_one(r); + assert_is_one(x); assert_is_one(b); + return 0b0100 << 4 | w << 3 | r << 2 | x << 1 | b; +} +#define REX_W rex(1,0,0,0) + +// ModR/M +// +// Addressing +// - Indirect : operand is stored under the address specified by one of the registers +// - Displaced: operand is stored under the displaced(offset) address specified by one of the registers, [rsp + 32h] other example: [rsp + rax*2 + 32], we use 2 registers - keyword SIB +// - Register : operand is stored in the register +// - Immediate: operand is stored right in the instruction, seems like existence of immeadiate depends on the instruction +enum{ // modr/m modes + MODE_INDIRECT_ADDRESSING = 0, + MODE_DISPLACED8_ADDRESSING = 1, + MODE_DISPLACED32_ADDRESSING = 2, + MODE_DISPLACED16_ADDRESSING = 2, + MODE_REGISTER_ADDRESSING = 3, +}; + +// SIB Addressing +// 7-6 bits for scale (1, 2, 4, 8) +// 5-3 register with index (index * scale) +// 2-0 register with base base + (index * scale) +const uint8_t SIB_SCALE1 = 0; +const uint8_t SIB_SCALE2 = 1; +const uint8_t SIB_SCALE4 = 2; +const uint8_t SIB_SCALE8 = 3; +const uint8_t MODRM_RM_SIB = 4; + +// reg, rm - specify 3 bit register +// to address extended registers r8 - r15, you need to set proper REX bit +uint8_t modrm(uint8_t mode, uint8_t reg, uint8_t rm) { + assert_in_range(mode, 0, 3); + assert_in_range(reg, 0, 7); + assert_in_range(rm, 0, 7); + return (mode << 6) | (reg << 3) | rm; +} + +// We need to set appropriate flag on the rex value to +// use extended registers +uint8_t adjust_registers_and_get_rex(Register *rm, Register *rx) { + uint8_t rex_value = REX_W; + assert_in_range(*rm, 0, 15); + assert_in_range(*rx, 0, 15); + + if (*rm >= 8) { + rex_value |= 1 << 2; + *rm -= 8; + } + + if (*rx >= 8) { + rex_value |= 1; + *rx -= 8; + } + + return rex_value; +} + +// add rax, rcx => emit_register_op(0x03, RAX, RCX) +void emit_register_op(uint8_t op_code, Register rm, Register rx) { + uint8_t rex_value = adjust_registers_and_get_rex(&rm, &rx); + + emit(rex_value); + emit(op_code); + emit(modrm(MODE_REGISTER_ADDRESSING, rm, rx)); +} + +// add rax, [rcx] => emit_indirect_op(0x03, RAX, RCX); +void emit_indirect_op(uint8_t op_code, Register rx, Register rm) { + assert(rx != RSP); // SIB + assert(rx != 8 + RSP); // SIB // R12 + assert(rx != RBP); // Displacement32 + assert(rx != 8 + RBP); // Displacement32 // R13 + + uint8_t rex_value = adjust_registers_and_get_rex(&rm, &rx); + emit(rex_value); + emit(op_code); + emit(modrm(MODE_INDIRECT_ADDRESSING, rm, rx)); +} + +void emit_indirect_displaced8_op(uint8_t op_code, Register rx, Register rm, uint8_t displacement) { + assert(rx != RSP); // SIB + assert(rx != 8 + RSP); // SIB // R12 + + uint8_t rex_value = adjust_registers_and_get_rex(&rm, &rx); + emit(rex_value); + emit(op_code); + emit(modrm(MODE_DISPLACED8_ADDRESSING, rm, rx)); + emit(displacement); +} + +void emit_indirect_displaced32_op(uint8_t op_code, Register rx, Register rm, uint32_t displacement) { + assert(rx != RSP); // SIB + assert(rx != 8 + RSP); // SIB // R12 + + uint8_t rex_value = adjust_registers_and_get_rex(&rm, &rx); + emit(rex_value); + emit(op_code); + emit(modrm(MODE_DISPLACED32_ADDRESSING, rm, rx)); + emit32(displacement); +} + + +// extern "C" void asm_test(); +void test_x64_stuff() { + //asm_test(); + + code = code_start = (uint8_t*)VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + emit(OP_BREAK); + + // REX.W + F7 /4 + #if 0 + emit(REX_W); + emit(0xF7); + emit(modrm(MODE_REGISTER_ADDRESSING, 0x4, RCX)); + #endif + + for (Register dst = RAX; dst <= R15; dst++) { + for (Register src = RAX; src <= R15; src++) { + if ((dst & 7) != RSP) { + emit_indirect_displaced32_op(0x03, dst, src, 0xffffff); + emit_indirect_displaced8_op(0x03, dst, src, 0x32); + } + if ((dst & 7) != RSP && (dst & 7) != RBP) + emit_indirect_op(0x03, dst, src); + emit_register_op(0x03, dst, src); + } + } + + emit(OP_RETURN); + + auto add = (uint64_t(*)(uint64_t, uint64_t))code_start; + uint64_t result = add(11, 22); +} \ No newline at end of file