289 lines
9.1 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include "lib.h"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
enum Instruction
{
INST_MOV_REG_REG = 0b10001000,
// INST_MOV_REG_REG = 0b10001000,
// INST_MOV_REG_REG = 0b10001000,
};
enum Mode
{
MODE_MEM_NO_DIS = 0b00,
MODE_MEM_DIS_08 = 0b01,
MODE_MEM_DIS_16 = 0b10,
MODE_RGSTR_MODE = 0b11,
};
typedef struct Register
{
char code;
char* fullname;
char* bytename;
union
{
struct
{
char low;
char high;
};
u16 full;
} value;
} Register;
Register registers[8] = {
{.code = 0b000, .fullname = "ax", .bytename = "al"},
{.code = 0b001, .fullname = "cx", .bytename = "cl"},
{.code = 0b010, .fullname = "dx", .bytename = "dl"},
{.code = 0b011, .fullname = "bx", .bytename = "bl"},
{.code = 0b100, .fullname = "sp", .bytename = "ah"},
{.code = 0b101, .fullname = "bp", .bytename = "ch"},
{.code = 0b110, .fullname = "si", .bytename = "dh"},
{.code = 0b111, .fullname = "di", .bytename = "bh"},
};
char* memory[65536];
// void inst_mov_rgmm_reg()
/// Get Effective Address Calculation Registers
char* get_eac_registers(char rm)
{
char* reg_name;
switch (rm)
{
case 0b000:
reg_name = "bx + si";
break;
case 0b001:
reg_name = "bx + di";
break;
case 0b010:
reg_name = "bp + si";
break;
case 0b011:
reg_name = "bp + di";
break;
case 0b100:
reg_name = "si";
break;
case 0b101:
reg_name = "di";
break;
case 0b110:
reg_name = "bp";
break;
case 0b111:
reg_name = "bx";
break;
default:
perror("Invalid R/M value");
exit(1);
}
return reg_name;
}
static inline char* reg_name(Register reg, char wide)
{
return wide == 1 ? reg.fullname : reg.bytename;
}
static inline i16 get_data(unsigned char* buf, char wide)
{
// Cast buf[0] to sbyte if not the conversion to i16 won't detect signedness
return wide == 1 ? (i16)buf[1] << 8 | buf[0] : (sbyte)buf[0];
}
bool mov_inst(FILE* f, unsigned char* buf, char inst)
{
size_t bytes_read;
// Register/memory to/from register
if ((inst & ~0x3) == (char)0b10001000)
{
// TODO: We should add some form of error handling here
bytes_read = fread(buf, sizeof(char), 1, f);
char next_byte = buf[0];
char w = inst & 0b00000001;
char d = (inst & 0b00000010) >> 1;
char mod = (next_byte & 0b11000000) >> 6;
char reg = (next_byte & 0b00111000) >> 3;
char rm = (next_byte & 0b00000111);
if (mod == MODE_RGSTR_MODE)
{
Register src_reg = d == 0 ? registers[(size_t)reg] : registers[(size_t)rm];
Register dst_reg = d == 0 ? registers[(size_t)rm] : registers[(size_t)reg];
printf("mov %s, %s ;0", reg_name(dst_reg, w), reg_name(src_reg, w));
}
else
{
bool is_direct_addr = mod == 0 && rm == 0b110;
// This is a trick because mod == 1 and mod == 2 will displace one and two bytes
// respectively but mod == 3 wraps to 0 since it doesn't displace
int bytes_to_read = is_direct_addr ? 2 : mod % 3;
bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
char* eac_name = is_direct_addr ? "" : get_eac_registers(rm);
char disp_buf[16] = {'\0'};
if (bytes_to_read > 0)
{
i16 disp = get_data(buf, bytes_to_read - 1);
if (is_direct_addr) sprintf(disp_buf, "%d", abs(disp));
else sprintf(disp_buf, " %s %d", disp >= 0 ? "+" : "-", abs(disp));
}
Register rgstr = registers[(size_t)reg];
if (d) printf("mov %s, [%s%s] ;1", reg_name(rgstr, w), eac_name, disp_buf);
else printf("mov [%s%s], %s ;2", eac_name, disp_buf, reg_name(rgstr, w));
}
}
// Immediate to register/memory
else if ((inst & ~0x1) == (char)0b11000110)
{
bytes_read = fread(buf, sizeof(char), 1, f);
char w = inst & 0b00000001;
char mod = (buf[0] & 0b11000000) >> 6;
char rm = (buf[0] & 0b00000111);
int bytes_to_read = 1;
bytes_to_read += w == 0 ? 0 : 1;
// Same trick from earlier, see comment
bytes_to_read += mod % 3;
bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
char *eac_name = get_eac_registers(rm);
i16 data = get_data(buf + (char)bytes_to_read - (w == 0 ? 1 : 2), w);
char *word_str = w == 0 ? "byte" : "word";
char disp_str[16] = {'\0'};
if (mod % 3 > 1) sprintf(disp_str, " + %d", get_data(buf, (mod % 3) - 1));
printf("mov [%s%s], %s %d ;3", eac_name, disp_str, word_str, data);
}
// Immediate to register
else if ((inst & ~0xF) == (char)0b10110000)
{
char w = (inst & 0b00001000) >> 3;
Register reg = registers[(size_t)inst & 0b00000111];
char bytes_to_read = w == 1 ? 2 : 1;
bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
printf("mov %s, %d ;4", reg_name(reg, w), get_data(buf, w));
}
// Memory/accumulator to accumulator/memory
else if ((inst & ~0x3) == (char)0b10100000)
{
// This instruction uses AX/AL register exclusively
Register ax_al = registers[0];
char w = (inst & 0b00000001);
// The manual doesn't refer to this as `d` but it acts similarly in that this bit
// swaps the accumulator's src/dst position
char d = (inst & 0b00000010) >> 1;
char bytes_to_read = w == 1 ? 2 : 1;
bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
if (d) printf("mov [%d], %s ;5", get_data(buf, w), reg_name(ax_al, w));
else printf("mov %s, [%d] ;6", reg_name(ax_al, w), get_data(buf, w));
}
// Register/memory to segment register or segment register to register/memory
else if ((inst & ~0x3) == (char)0b10001100)
{
// Manual doesn't refer to this as `d` but swaps like in the previous instruction
char d = (inst & 0b00000010) >> 1;
(void)d;
printf("mov regmem to segreg");
}
else
{
return false;
}
return bytes_read > 0;
}
bool add_inst(FILE* f, unsigned char* buf, char inst)
{
size_t bytes_read;
if ((inst & ~0x3) == (char)0b00000000)
{
bytes_read = fread(buf, sizeof(char), 1, f);
char next_byte = buf[0];
char w = inst & 0b00000001;
char d = (inst & 0b00000010) >> 1;
char mod = (next_byte & 0b11000000) >> 6;
char reg = (next_byte & 0b00111000) >> 3;
char rm = (next_byte & 0b00000111);
// Same trick from earlier, see comment
int bytes_to_read = mod % 3;
if (bytes_to_read > 0) bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
Register rgstr = registers[(size_t)reg];
(void)rm;
if (mod == MODE_RGSTR_MODE)
{
if (d) printf("add %s, [%d] ;7", reg_name(rgstr, w), get_data(buf, w));
else printf("add [%d], %s ;8", get_data(buf, w), reg_name(rgstr, w));
}
else if (mod == MODE_MEM_NO_DIS)
{
if (d) printf("add %s, [%s] ;9", reg_name(rgstr, w), get_eac_registers(rm));
else printf("add [%s], %s ;10", get_eac_registers(rm), reg_name(rgstr, w));
}
else
{
if (d) printf("add %s, [%s] ;11", reg_name(rgstr, w), get_eac_registers(rm));
else printf("add [%s], %s ;12", get_eac_registers(rm), reg_name(rgstr, w));
}
}
else if ((inst & ~0x3) == (char)0b10000000)
{
bytes_read = fread(buf, sizeof(char), 1, f);
char w = inst & 0b00000001;
char mod = (buf[0] & 0b11000000) >> 6;
char rm = (buf[0] & 0b00000111);
int bytes_to_read = 1;
bytes_to_read += w == 0 ? 1 : 2;
// Same trick from earlier, see comment
bytes_to_read += mod % 3;
bytes_read = fread(buf, sizeof(char), bytes_to_read, f);
char *eac_name = get_eac_registers(rm);
i16 data = get_data(buf + (char)bytes_to_read - (w == 0 ? 1 : 2), w);
char *word_str = w == 0 ? "byte" : "word";
char disp_str[16] = {'\0'};
if (mod % 3 > 1) sprintf(disp_str, " + %d", get_data(buf, (mod % 3) - 1));
printf("add [%s%s], %s %d ;13", eac_name, disp_str, word_str, data);
}
else
{
return false;
}
return bytes_read > 0;
}
int main(int argc, char** argv)
{
if (argc < 2)
{
printf("Usage: Please provide assembled instructions as input\n");
exit(0);
}
unsigned char buf[256];
FILE *f = fopen(argv[1], "r");
if (!f)
{
perror("fopen\n");
return EXIT_FAILURE;
}
size_t bytes_read;
printf("; Decoded 8086 Assembly Instructions\n\n");
printf("bits 16\n\n");
while ((bytes_read = fread(buf, sizeof(char), 1, f)) > 0)
{
char inst = buf[0];
if (mov_inst(f, buf, inst)) goto handled;
if (add_inst(f, buf, inst)) goto handled;
fprintf(stderr, "___Unrecognized Instruction___");
handled:
printf("\n");
}
}