diff options
author | dec05eba <dec05eba@protonmail.com> | 2019-07-31 01:25:05 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2020-07-25 14:36:46 +0200 |
commit | 1f28c3c733ea3ae4234bff91e1c55e61b1ee3e96 (patch) | |
tree | 0ab52e362da03fde741ce8159ef8a4110cd1fb6a /src/asm | |
parent | ec1a48e7b86fcd00127dd5a88d56c42083af1d78 (diff) |
Starting on asm, implementing extern function call so progress is visible
Diffstat (limited to 'src/asm')
-rw-r--r-- | src/asm/x86_64.c | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/src/asm/x86_64.c b/src/asm/x86_64.c new file mode 100644 index 0000000..2cbeead --- /dev/null +++ b/src/asm/x86_64.c @@ -0,0 +1,383 @@ +#include "../../include/asm/x86_64.h" + +#include "../../include/std/mem.h" +#include "../../include/std/log.h" +#include <errno.h> +#include <assert.h> +#include <stdio.h> + +#include <sys/mman.h> + +#define REX_W 0x48 + +void asm_ptr_init(AsmPtr *self, Reg64 base) { + self->base = base; + self->index = -1; + self->disp = 0; + self->scale = 0; +} + +void asm_ptr_init_index(AsmPtr *self, Reg64 base, Reg64 index) { + self->base = base; + self->index = index; + self->disp = 0; + self->scale = 0; +} + +void asm_ptr_init_disp(AsmPtr *self, Reg64 base, i32 disp) { + self->base = base; + self->index = -1; + self->disp = disp; + self->scale = 0; +} + +void asm_ptr_init_index_disp(AsmPtr *self, Reg64 base, Reg64 index, i32 disp) { + self->base = base; + self->index = index; + self->disp = disp; + self->scale = 0; +} + +int asm_init(Asm *self) { + self->size = am_pagesize(); + amal_log_debug("asm: page size: %u", self->size); + self->code = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if(self->code == MAP_FAILED) + return -errno; + self->code_it = self->code; + return 0; +} + +void asm_deinit(Asm *self) { + if(self->code) + munmap(self->code, self->size); + self->code = NULL; + self->code_it = NULL; + self->size = 0; +} + +static void asm_print_code_hex(Asm *self) { + u8 *ptr; + int off; + ptr = self->code; + off = 0; + while(ptr != self->code_it) { + printf("%02x", *ptr); + ++ptr; + ++off; + if(off == 8) { + putc('\n', stdout); + off = 0; + } else { + putc(' ', stdout); + } + } + if(off != 0) + putc('\n', stdout); +} + +int asm_execute(Asm *self) { + void (*func)(); + if(mprotect(self->code, self->size, PROT_READ | PROT_EXEC) != 0) + return -errno; + + asm_print_code_hex(self); + + /* TODO: Verify if this is valid on all platforms. According to ISO C standard it isn't? */ + *(void**)(&func) = self->code; + func(); + return 0; +} + +/* TODO: See how this can be optimized */ +static CHECK_RESULT int asm_ensure_capacity(Asm *self, usize size) { + usize current_offset; + current_offset = (u8*)self->code_it - (u8*)self->code; + if(current_offset + size > self->size) { + void *new_mem; + usize new_size; + new_size = self->size + am_pagesize(); + new_mem = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if(self->code == MAP_FAILED) + return -errno; + + am_memcpy(new_mem, self->code, self->size); + self->code = new_mem; + self->size = new_size; + self->code_it = (u8*)self->code + current_offset; + } + return 0; +} + +static isize asm_get_capacity_left(Asm *self) { + return (isize)self->size - (isize)((u8*)self->code_it - (u8*)self->code); +} + +int asm_nop(Asm *self) { + return_if_error(asm_ensure_capacity(self, 1)); + *self->code_it++ = 0x90; + return 0; +} + +static i32 abs_i32(i32 value) { + return value >= 0 ? value : -value; +} + +/* +TODO: Implement 1 and 2 byte displacement? +There has to be at least 6 bytes left in the asm buffer before calling this function. +*/ +static void asm_rm(Asm *self, AsmPtr *mem, Reg64 reg) { + u8 rm_byte; + u8 disp_bytes; + assert(asm_get_capacity_left(self) >= 6); + if((int)mem->index != -1) { + u8 sib_offset; + if(mem->disp == 0) { + rm_byte = 0x04; + disp_bytes = 0; + } else if(abs_i32(mem->disp) <= INT8_MAX) { + rm_byte = 0x44; + disp_bytes = 1; + } else { + rm_byte = 0x84; + disp_bytes = 4; + } + + #ifdef DEBUG + if(mem->scale != 0 && mem->scale != 2 && mem->scale != 4 && mem->scale != 8) { + amal_log_error("Invalid scale %d, expected 0, 2, 4, or 8", mem->scale); + assert(bool_false); + } + #endif + assert(mem->base != RBP && "TODO: Implement RBP base for sib byte. RBP is special and requires different logic"); + sib_offset = (mem->scale << 5) + 8*mem->index + mem->base; + + *self->code_it++ = rm_byte; + *self->code_it++ = sib_offset; + } else { + if(mem->disp == 0) { + if(mem->base == RBP) { + rm_byte = 0x45; + disp_bytes = 1; + } else { + rm_byte = mem->base; + disp_bytes = 0; + } + } else if(abs_i32(mem->disp) <= INT8_MAX) { + rm_byte = 0x40 + mem->base; + disp_bytes = 1; + } else { + rm_byte = 0x80 + mem->base; + disp_bytes = 4; + } + *self->code_it++ = (reg << 3) | rm_byte; + } + + am_memcpy(self->code_it, &mem->disp, disp_bytes); + self->code_it += disp_bytes; +} + +/* There has to be at least 1 byte left in the asm buffer before calling this function. */ +static void asm_rr(Asm *self, Reg64 dst, Reg64 src) { + assert(asm_get_capacity_left(self) >= 1); + *self->code_it++ = 0xC0 + dst + 8*src; +} + +/* TODO: Implement 1 and 2 byte immediate? */ +int asm_mov_mi(Asm *self, AsmPtr *dst, i32 immediate) { + /* 12 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 12)); + *self->code_it++ = REX_W; + *self->code_it++ = 0xC7; + asm_rm(self, dst, 0); + am_memcpy(self->code_it, &immediate, sizeof(immediate)); + self->code_it += sizeof(immediate); + return 0; +} + +int asm_mov_mr(Asm *self, AsmPtr *dst, Reg64 src) { + /* 8 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 8)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x89; + asm_rm(self, dst, src); + return 0; +} + +int asm_mov_rm(Asm *self, Reg64 dst, AsmPtr *src) { + /* 8 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 8)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x8B; + asm_rm(self, src, dst); + return 0; +} + +int asm_mov_ri(Asm *self, Reg64 dst, i64 immediate) { + /* 10 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 10)); + *self->code_it++ = REX_W; + *self->code_it++ = 0xB8 + dst; + am_memcpy(self->code_it, &immediate, sizeof(immediate)); + self->code_it += sizeof(immediate); + return 0; +} + +int asm_mov_rr(Asm *self, Reg64 dst, Reg64 src) { + /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 3)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x89; + asm_rr(self, dst, src); + return 0; +} + +int asm_add_rr(Asm *self, Reg64 dst, Reg64 src) { + /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 3)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x01; + asm_rr(self, dst, src); + return 0; +} + +int asm_sub_rr(Asm *self, Reg64 dst, Reg64 src) { + /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 3)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x29; + asm_rr(self, dst, src); + return 0; +} + +int asm_imul_rr(Asm *self, Reg64 dst, Reg64 src) { + /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ + return_if_error(asm_ensure_capacity(self, 4)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x0F; + *self->code_it++ = 0xAF; + asm_rr(self, dst, src); + return 0; +} + +int asm_pushr(Asm *self, Reg64 reg) { + return_if_error(asm_ensure_capacity(self, 1)); + *self->code_it++ = 0x50 + reg; + return 0; +} + +int asm_popr(Asm *self, Reg64 reg) { + return_if_error(asm_ensure_capacity(self, 1)); + *self->code_it++ = 0x58 + reg; + return 0; +} + +/* /r */ +#define DEFINE_INS_RM(mnemonic, opcode) \ +int asm_##mnemonic##_rm32(Asm *self, Reg32 dst, Reg32 src) { \ + return_if_error(asm_ensure_capacity(self, 2)); \ + *self->code_it++ = opcode; \ + *self->code_it++ = 0xC0 + 8*dst + src; \ + return 0; \ +} \ + \ +int asm_##mnemonic##_rm64(Asm *self, Reg64 dst, Reg64 src) { \ + return_if_error(asm_ensure_capacity(self, 1)); \ + *self->code_it++ = REX_W; \ + return asm_##mnemonic##_rm32(self, (Reg32)dst, (Reg32)src); \ +} + +DEFINE_INS_RM(mov, 0x8B) +DEFINE_INS_RM(add, 0x03) +DEFINE_INS_RM(sub, 0x2B) +DEFINE_INS_RM(and, 0x23) +DEFINE_INS_RM(or, 0x0B) +DEFINE_INS_RM(xor, 0x33) +DEFINE_INS_RM(cmp, 0x3B) + +/* + /number + The number is called the extension, a number from 0 to 7; + It's a number used to extend the opcode type, since the instruction only uses + one register the other register can be encoded for that. +*/ +#define DEFINE_INS_EXT_IMM(mnemonic, extension) \ +int asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i32 immediate) { \ + if(abs_i32(immediate) <= INT8_MAX) { \ + return_if_error(asm_ensure_capacity(self, 3)); \ + *self->code_it++ = 0x83; \ + *self->code_it++ = 0xC0 + 8*extension + reg; \ + *self->code_it++ = (u8)immediate; \ + } else { \ + return_if_error(asm_ensure_capacity(self, 6)); \ + *self->code_it++ = 0x81; \ + *self->code_it++ = 0xC0 + 8*extension + reg; \ + am_memcpy(self->code_it, &immediate, sizeof(immediate)); \ + self->code_it += sizeof(immediate); \ + } \ + return 0; \ +} \ + \ +int asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i32 immediate) { \ + return_if_error(asm_ensure_capacity(self, 1)); \ + *self->code_it++ = REX_W; \ + return asm_##mnemonic##_rm32_imm(self, (Reg32)reg, immediate); \ +} + +DEFINE_INS_EXT_IMM(add, 0) +DEFINE_INS_EXT_IMM(or, 1) +DEFINE_INS_EXT_IMM(adc, 2) +DEFINE_INS_EXT_IMM(sbb, 3) +DEFINE_INS_EXT_IMM(and, 4) +DEFINE_INS_EXT_IMM(sub, 5) +DEFINE_INS_EXT_IMM(xor, 6) +DEFINE_INS_EXT_IMM(cmp, 7) + +/* + /number + The number is called the extension, a number from 0 to 7; + It's a number used to extend the opcode type, since the instruction only uses + one register the other register can be encoded for that. +*/ +#define DEFINE_INS_SHIFT_IMM8(mnemonic, extension) \ +int asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i8 immediate) { \ + if(immediate == 1) { \ + return_if_error(asm_ensure_capacity(self, 2)); \ + *self->code_it++ = 0xC1; \ + *self->code_it++ = 0xC0 + 8*reg + extension; \ + } else { \ + return_if_error(asm_ensure_capacity(self, 3)); \ + *self->code_it++ = 0xD1; \ + *self->code_it++ = 0xC0 + 8*reg + extension; \ + *self->code_it++ = immediate; \ + } \ + return 0; \ +} \ + \ +int asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i8 immediate) { \ + return_if_error(asm_ensure_capacity(self, 1)); \ + *self->code_it++ = REX_W; \ + return asm_##mnemonic##_rm32_imm(self, (Reg32)reg, immediate); \ +} + +DEFINE_INS_SHIFT_IMM8(rol, 0) +DEFINE_INS_SHIFT_IMM8(ror, 1) +DEFINE_INS_SHIFT_IMM8(rcl, 2) +DEFINE_INS_SHIFT_IMM8(rcr, 3) +DEFINE_INS_SHIFT_IMM8(shl, 4) +DEFINE_INS_SHIFT_IMM8(shr, 5) +/*DEFINE_INS_SHIFT_IMM8(shl, 6)*/ +DEFINE_INS_SHIFT_IMM8(sar, 7) + +int asm_ret(Asm *self, u16 bytes) { + if(bytes == 0) { + return_if_error(asm_ensure_capacity(self, 1)); + *self->code_it++ = 0xC3; + } else { + return_if_error(asm_ensure_capacity(self, 3)); + *self->code_it++ = 0xC2; + am_memcpy(self->code_it, &bytes, sizeof(bytes)); + } + return 0; +}
\ No newline at end of file |