├── bios-bochs ├── minix-floppy.img ├── .gitignore ├── include ├── ui.h ├── disasm.h ├── bitwise.h ├── loaders.h ├── instruction.h ├── errors.h └── cpu.h ├── config.h ├── README.md ├── main.c ├── bitwise.c ├── Makefile ├── frontends └── text │ └── ui.c ├── loaders.c └── cpu.c /bios-bochs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highwaycoder/8086/HEAD/bios-bochs -------------------------------------------------------------------------------- /minix-floppy.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highwaycoder/8086/HEAD/minix-floppy.img -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | architecture-notes 3 | *.o 4 | 8086 5 | core.dmp 6 | .bochsrc 7 | lib 8 | -------------------------------------------------------------------------------- /include/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_H 2 | #define UI_H 3 | 4 | #ifndef CPU_H 5 | #include "cpu.h" 6 | #endif 7 | 8 | void ui_step(cpu_t* cpu); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/disasm.h: -------------------------------------------------------------------------------- 1 | #ifndef DISASM_H 2 | #define DISASM_H 3 | 4 | #ifndef INSTRUCTION_H 5 | #include "instruction.h" 6 | #endif 7 | 8 | void disasm_opcode(opcode_t opcode,char* buf); 9 | void disasm_modrm(uint8_t modrm,char* buf); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define USAGE "Usage: 8086 floppy_image\n\ 5 | Where \"floppy_image\" is the file you want to boot from.\n" 6 | 7 | extern char BUILD_DATE; 8 | extern char BUILD_NUMBER; 9 | 10 | #define print_copyright() printf("8086 Version %.2f © Chris Browne 2011-2012.\nBuild date: %u. Build no.: %04u\n",VERSION,(unsigned long) &BUILD_DATE, (unsigned long) &BUILD_NUMBER) 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 8086 Emulator 2 | Prior to writing my Chip8 emulator, a suggestion was floated that 3 | tumblrcode should try emulating the 8086 architecture. 4 | It was discussed, and decided that first we should all make sure we 5 | can write basic emulators so I went away and got my Chip8 one working. 6 | Now the hour of our crusade has dawned, and the perilous journey through 7 | 8086 architecture manuals, instruction sets, opcode maps and datasheets 8 | has begun. 9 | -------------------------------------------------------------------------------- /include/bitwise.h: -------------------------------------------------------------------------------- 1 | #ifndef BITWISE_H 2 | #define BITWISE_H 3 | 4 | // I've realised that I can exploit header guards to speed up compilation 5 | // I'm not sure how effective it is, because this project hasn't grown 6 | // enough yet but hopefully it'll save some time later on 7 | #ifndef CPU_H 8 | #include "cpu.h" 9 | #endif 10 | 11 | void xor(cpu_t* cpu); 12 | 13 | void clear_reg(uint16_t* dest); 14 | void xor_reg16_reg16(uint16_t* dest,uint16_t src); 15 | void xor_modrm_reg16(cpu_t* cpu,uint8_t modrm,uint16_t src); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /include/loaders.h: -------------------------------------------------------------------------------- 1 | #ifndef LOADERS_H 2 | #define LOADERS_H 3 | 4 | void mov(cpu_t* cpu); 5 | void mov_reg16_imm(uint16_t* dest,uint16_t src); 6 | void mov_regLO_imm(uint16_t* dest,uint8_t src); 7 | void mov_regHO_imm(uint16_t* dest,uint8_t src); 8 | void mov_reg16_reg16(uint16_t* dest,uint16_t src); 9 | void mov_regLO_regLO(uint16_t* dest,uint16_t src); 10 | void mov_regHO_regHO(uint16_t* dest,uint16_t src); 11 | void mov_regLO_regHO(uint16_t* dest,uint16_t src); 12 | void mov_reg16_regHO(uint16_t* dest,uint16_t src); 13 | void mov_reg16_regLO(uint16_t* dest,uint16_t src); 14 | 15 | 16 | #endif // LOADERS_H 17 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "cpu.h" 4 | #include "errors.h" 5 | #include "config.h" 6 | 7 | int main(int argc, char** argv) 8 | { 9 | FILE* bios_file = fopen("bios-bochs","r"); 10 | cpu_t* cpu = new_cpu(bios_file); 11 | int rv = 0; 12 | FILE* floppy = NULL; 13 | print_copyright(); 14 | printf("\n"); 15 | if(cpu == NULL) 16 | { 17 | fprintf(stderr,"Couldn't init CPU. Try again please.\n"); 18 | rv = -1; 19 | } 20 | if(argc == 2) 21 | { 22 | floppy = fopen(argv[1],"r"); 23 | } 24 | else 25 | { 26 | fprintf(stderr,USAGE); 27 | return -1; 28 | } 29 | rv = boot_from_floppy(cpu,floppy); 30 | if(!rv) 31 | { 32 | run(cpu); 33 | } 34 | else 35 | { 36 | cpu->errno = ENO_BOOT_MEDIUM; 37 | } 38 | dump_state(*cpu); 39 | free_cpu(cpu); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /bitwise.c: -------------------------------------------------------------------------------- 1 | #include "instruction.h" 2 | #include "cpu.h" 3 | #include "bitwise.h" 4 | 5 | void xor(cpu_t* cpu) 6 | { 7 | switch(cpu->memory[cpu->ip]) 8 | { 9 | case 0x31: 10 | xor_modrm_reg16(cpu,cpu->memory[cpu->ip+1],cpu->memory[cpu->ip+2]); 11 | cpu->ip += instruction_length(0x31); 12 | break; 13 | } 14 | } 15 | 16 | void xor_modrm_reg16(cpu_t* cpu,uint8_t modrm,uint16_t src) 17 | { 18 | switch(modrm) 19 | { 20 | case 0xC0: 21 | // 0x31C0 is XOR ax,ax. I think the 0x31 opcode is XOR reg,self 22 | // ie "clear reg", so I'm going to write a function that does exactly that. 23 | clear_reg(&cpu->ax); 24 | break; 25 | } 26 | } 27 | 28 | void clear_reg(uint16_t* dest) 29 | { 30 | // equivalent to *dest ^= *dest but clearer 31 | *dest = 0x0; 32 | } 33 | 34 | void xor_reg16_reg16(uint16_t* dest,uint16_t src) 35 | { 36 | *dest ^= src; 37 | } 38 | -------------------------------------------------------------------------------- /include/instruction.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTRUCTION_H 2 | #define INSTRUCTION_H 3 | #include 4 | 5 | // again, fuck unions for this kind of work 6 | typedef uint8_t opcode_t; 7 | 8 | // a useful macro 9 | #define SET_OPCODE(dest,src) { dest.op = src >> 2; dest.d = (src & 0x2) >> 1; dest.w = src & 0x1; } 10 | 11 | static inline unsigned int instruction_length(opcode_t opcode) 12 | { 13 | unsigned int rv = 0; 14 | switch(opcode) 15 | { 16 | // opcode with zero operands (total instruction length==1) 17 | 18 | // opcodes with one operand (total instruction length==2) 19 | case 0x31: 20 | case 0xB8 ... 0xBF: 21 | default: // making the assumption that most instructions take one operand is probably dangerous, but oh well 22 | rv = 2; 23 | break; 24 | // opcodes with two operands (total instruction length==3) 25 | } 26 | return rv; 27 | } 28 | 29 | #endif // INSTRUCTION_H 30 | -------------------------------------------------------------------------------- /include/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef ERRORS_H 2 | #define ERRORS_H 3 | 4 | /* 5 | * Notes: 6 | * flow error is 0Fxx 7 | * - overflow is xxx0 8 | * - underflow is xxx1 9 | * - so *flow can be checked with "((&0xFF00) >> 8) == 0x0F" 10 | * - and then over/underflow can be checked with "(&0x1)==1" 11 | * - I don't foresee it being used much like this though, but it's 12 | * nice to have a convention anyway 13 | * 14 | * errors beginning with BD relate to errors in a program, not errors 15 | * in the emulator. For example, if a program uses an opcode that does 16 | * not exist or is incompatible, BD0C will be set. 17 | * 18 | * errors beginning with EE relate to foreseeable errors with the 19 | * emulator and should be reported to someone who seems to know what 20 | * they're doing ASAP. If you can't find someone who seems to know what 21 | * they're doing, report them to synirc.net/#tumblrcode and someone 22 | * might help you (don't hold your breath though). 23 | */ 24 | 25 | 26 | enum ERRORS { 27 | ENONE = 0x0000, 28 | 29 | // overflows/underflows 30 | ESTACK_OVERFLOW = 0x0F00, 31 | ESTACK_UNDERFLOW = 0x0F01, 32 | EIP_OVERFLOW = 0x0F10, 33 | EIP_UNDERFLOW = 0x0F11, 34 | 35 | // BIOS-related errors 36 | ENO_BOOT_MEDIUM = 0xB007, 37 | 38 | // misc 39 | EBADOPCODE = 0xBD0C, 40 | 41 | // foreseeable errors in our emulator 42 | EUNIMPLEMENTED = 0xEE01, 43 | }; 44 | 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | LD=gcc 3 | CFLAGS=-fms-extensions -std=gnu99 -g -Wall -Wextra -Iinclude -DVERSION=1.0 4 | LDFLAGS :=-Xlinker --defsym -Xlinker BUILD_NUMBER=$$(cat build_number) 5 | LDFLAGS += -Xlinker --defsym -Xlinker BUILD_DATE=$$(date +'%Y%m%d') 6 | LDFLAGS += -Llib -lui 7 | PROGNAME=8086 8 | OBJS=main.o loaders.o cpu.o bitwise.o disasm.o 9 | 10 | # below are listed all the available frontends, the default is 'text' 11 | FRONTEND=text 12 | 13 | $(PROGNAME): $(OBJS) lib/libui.a build_number 14 | $(LD) $(LDFLAGS) -o $(PROGNAME) $(OBJS) lib/libui.a 15 | 16 | lib/libui.a: frontends/$(FRONTEND)/ui.c include/ui.h | lib 17 | $(CC) $(CFLAGS) -o frontends/$(FRONTEND)/ui.o -c frontends/$(FRONTEND)/ui.c 18 | $(AR) rcs $@ frontends/$(FRONTEND)/ui.o 19 | 20 | main.o: main.c include/cpu.h config.h include/errors.h 21 | $(CC) $(CFLAGS) -o main.o -c main.c 22 | 23 | loaders.o: loaders.c include/loaders.h include/cpu.h include/instruction.h 24 | $(CC) $(CFLAGS) -o loaders.o -c loaders.c 25 | 26 | cpu.o:cpu.c include/cpu.h include/loaders.h include/bitwise.h include/cpu.h include/errors.h 27 | $(CC) $(CFLAGS) -o cpu.o -c cpu.c 28 | 29 | bitwise.o: bitwise.c include/bitwise.h 30 | $(CC) $(CFLAGS) -o bitwise.o -c bitwise.c 31 | 32 | disasm.o: disasm.c include/disasm.h 33 | $(CC) $(CFLAGS) -o disasm.o -c disasm.c 34 | 35 | build_number: $(OBJS) 36 | @if ! test -f build_number; then echo 0 > build_number; fi 37 | echo $$(($$(cat build_number)+1)) > build_number 38 | 39 | lib: 40 | mkdir -p lib 41 | 42 | clean: clean-objs 43 | rm -f $(PROGNAME) 44 | 45 | clean-objs: 46 | rm -f $(OBJS) 47 | -------------------------------------------------------------------------------- /include/cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef CPU_H 2 | #define CPU_H 3 | #include 4 | #include // for FILE* 5 | 6 | // configuration constants 7 | #define MEMORY_SIZE (1024*1024) // maximum addressable memory is 1MiB 8 | #define MAX_MEMORY (MEMORY_SIZE-1) // highest addressable point 9 | 10 | // useful macro for accessing hi/lo accumulator bytes 11 | #define ACC_HI(x) (x >> 8) 12 | #define ACC_LO(x) (x & 0xFF) 13 | 14 | typedef enum POWER { 15 | ON, 16 | OFF 17 | } power_t; 18 | 19 | // fuck unions, let's just use a uint16_t for simplicity's sake 20 | typedef uint16_t acc_t; 21 | 22 | 23 | typedef struct CPU { 24 | // accumulators 25 | acc_t ax; 26 | acc_t bx; 27 | acc_t cx; 28 | acc_t dx; 29 | 30 | // index registers 31 | uint16_t si; 32 | uint16_t di; 33 | uint16_t bp; 34 | uint16_t sp; 35 | 36 | // status register 37 | uint16_t flags; 38 | 39 | // segment registers 40 | uint16_t cs; 41 | uint16_t ds; 42 | uint16_t es; 43 | uint16_t ss; 44 | 45 | // instruction pointer 46 | // must be able to address at least 1MiB of memory 47 | // (must be at least 20 bits wide therefore) 48 | unsigned int ip:20; 49 | 50 | // memory is now implemented as an array to protect it (does hurt the stack a bit though) 51 | uint8_t memory[MEMORY_SIZE]; 52 | 53 | // cpu error number for debugging purposes 54 | uint16_t errno; 55 | 56 | // power button, useful for shutting down safely 57 | power_t power; 58 | } cpu_t; 59 | 60 | // functions 61 | cpu_t* new_cpu(FILE* bios_file); 62 | int boot_from_floppy(cpu_t* cpu,FILE* ramfile); 63 | void free_cpu(cpu_t* cpu); 64 | void run(cpu_t* cpu); 65 | void step(cpu_t* cpu); 66 | void dump_core(cpu_t cpu); 67 | void dump_state(cpu_t cpu); 68 | void err2str(char* string,uint16_t errnum); 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /frontends/text/ui.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ui.h" 5 | #include "disasm.h" 6 | #include "cpu.h" 7 | 8 | // available commands 9 | typedef struct command_struct 10 | { 11 | enum { 12 | COMMAND_UNKNOWN, 13 | COMMAND_QUIT, 14 | COMMAND_STEP, 15 | COMMAND_INFO, 16 | } parsed_command; 17 | char* text; 18 | } command_t; 19 | 20 | command_t commands[] = { 21 | {COMMAND_UNKNOWN,""}, 22 | {COMMAND_QUIT,"quit"}, 23 | {COMMAND_QUIT,"exit"}, 24 | {COMMAND_STEP,"step"}, 25 | {COMMAND_STEP,"s"}, 26 | {COMMAND_INFO,"info"}, 27 | {COMMAND_INFO,"i"} 28 | }; 29 | 30 | // functions specific to the text-based frontend 31 | void prompt(cpu_t cpu); 32 | command_t parse_command(char* command); 33 | 34 | void ui_step(cpu_t* cpu) 35 | { 36 | char* command_buf = NULL; 37 | size_t line_size; 38 | command_t cmd; 39 | prompt(*cpu); 40 | getline(&command_buf,&line_size,stdin); 41 | command_buf[strlen(command_buf)-1] = 0; 42 | 43 | // this if statement mimics gdb's ability to remember your last command when you hit 'enter' without typing a command 44 | if(strcmp(command_buf,"") != 0) 45 | cmd = parse_command(command_buf); 46 | 47 | switch(cmd.parsed_command) 48 | { 49 | case COMMAND_QUIT: 50 | printf("Shutting down...\n"); 51 | cpu->power = OFF; 52 | break; 53 | case COMMAND_STEP: 54 | step(cpu); 55 | break; 56 | case COMMAND_INFO: 57 | dump_state(*cpu); 58 | break; 59 | default: 60 | printf("Unknown command: %s\n",command_buf); 61 | break; 62 | } 63 | free(command_buf); 64 | } 65 | 66 | void prompt(cpu_t cpu) 67 | { 68 | printf("[%.6X] Next Opcode: %.2X\n>",cpu.ip,cpu.memory[cpu.ip]); 69 | } 70 | 71 | command_t parse_command(char* command) 72 | { 73 | unsigned int i = 0; 74 | command_t rv = commands[0]; 75 | while(i < sizeof commands / sizeof commands[0]) 76 | { 77 | if(strcmp(commands[i].text,command)==0) 78 | rv = commands[i]; 79 | i++; 80 | } 81 | return rv; 82 | } 83 | -------------------------------------------------------------------------------- /loaders.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cpu.h" 3 | #include "instruction.h" 4 | #include "loaders.h" 5 | 6 | void mov(cpu_t* cpu) 7 | { 8 | switch(cpu->memory[cpu->ip]) 9 | { 10 | case 0xB8: 11 | mov_reg16_imm(&(cpu->ax),cpu->memory[cpu->ip+1]); 12 | break; 13 | case 0xB9: 14 | mov_reg16_imm(&(cpu->cx),cpu->memory[cpu->ip+1]); 15 | break; 16 | case 0xBA: 17 | mov_reg16_imm(&(cpu->dx),cpu->memory[cpu->ip+1]); 18 | break; 19 | case 0xBB: 20 | mov_reg16_imm(&(cpu->bx),cpu->memory[cpu->ip+1]); 21 | break; 22 | case 0xBC: 23 | mov_reg16_imm(&(cpu->sp),cpu->memory[cpu->ip+1]); 24 | break; 25 | case 0xBD: 26 | mov_reg16_imm(&(cpu->bp),cpu->memory[cpu->ip+1]); 27 | break; 28 | case 0xBE: 29 | mov_reg16_imm(&(cpu->si),cpu->memory[cpu->ip+1]); 30 | break; 31 | case 0xBF: 32 | mov_reg16_imm(&(cpu->di),cpu->memory[cpu->ip+1]); 33 | break; 34 | } 35 | // make sure to increment at the end 36 | cpu->ip += instruction_length(cpu->memory[cpu->ip]); 37 | } 38 | 39 | void mov_reg16_imm(uint16_t* dest,uint16_t src) 40 | { 41 | // this one is easy because it's a 16 bit immediate value into a 16 bit register 42 | *dest = src; 43 | } 44 | 45 | void mov_regLO_imm(uint16_t* dest,uint8_t src) 46 | { 47 | *dest = (*dest & 0xFF00) | src; 48 | } 49 | 50 | void mov_regHO_imm(uint16_t* dest,uint8_t src) 51 | { 52 | *dest = (*dest & 0x00FF) | (src << 8); 53 | } 54 | 55 | void mov_reg16_reg16(uint16_t* dest,uint16_t src) 56 | { 57 | *dest = src; 58 | } 59 | 60 | void mov_regLO_regLO(uint16_t* dest,uint16_t src) 61 | { 62 | *dest = (*dest & 0xFF00) | (src & 0x00FF); 63 | } 64 | 65 | void mov_regHO_regHO(uint16_t* dest,uint16_t src) 66 | { 67 | *dest = (*dest & 0x00FF) | (src & 0xFF00); 68 | } 69 | 70 | void mov_regLO_regHO(uint16_t* dest,uint16_t src) 71 | { 72 | *dest = (*dest & 0xFF00) | ((src & 0xFF00) >> 8); 73 | } 74 | 75 | void mov_reg16_regHO(uint16_t* dest,uint16_t src) 76 | { 77 | // this nukes *dest then sets the HO byte to match src's HO byte 78 | *dest = (*dest & 0x0000) | (src & 0xFF00); 79 | } 80 | 81 | void mov_reg16_regLO(uint16_t* dest,uint16_t src) 82 | { 83 | // same as above, only with LO bytes 84 | *dest = (*dest & 0x0000) | (src & 0x00FF); 85 | } 86 | -------------------------------------------------------------------------------- /cpu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "cpu.h" 6 | #include "loaders.h" 7 | #include "errors.h" 8 | #include "bitwise.h" 9 | #include "instruction.h" // for instruction_length 10 | 11 | cpu_t* new_cpu(FILE* bios_file) 12 | { 13 | cpu_t* rv = malloc(sizeof(cpu_t)); 14 | if(rv != NULL) 15 | { 16 | memset(rv,0,sizeof(cpu_t)); 17 | // now read the BIOS into memory at 0xC0000 to 0xF0000 18 | fread(rv->memory+0xC0000,0x20000,1,bios_file); 19 | // now set the instruction pointer to start at the BIOS's start 20 | rv->ip = 0xC0000; 21 | } 22 | rv->power = ON; 23 | return rv; 24 | } 25 | 26 | int boot_from_floppy(cpu_t* cpu,FILE* floppy) 27 | { 28 | if(floppy == NULL) 29 | { 30 | return -1; 31 | } 32 | // read the file into memory 33 | fread(cpu->memory,MEMORY_SIZE,1,floppy); 34 | return ferror(floppy); 35 | } 36 | 37 | // keeping this in even though it's just an alias for free() at the moment 38 | // it may come in handy later, depending what happens to the cpu_t type 39 | void free_cpu(cpu_t* cpu) 40 | { 41 | if(cpu==NULL) 42 | return; 43 | free(cpu); 44 | return; 45 | } 46 | 47 | void run(cpu_t* cpu) 48 | { 49 | do 50 | { 51 | ui_step(cpu); 52 | } while(cpu->power == ON && cpu->errno == ENONE); 53 | } 54 | 55 | void step(cpu_t* cpu) 56 | { 57 | switch(cpu->memory[cpu->ip]) 58 | { 59 | // MOV family of instructions 60 | case 0xB8 ... 0xBF: 61 | mov(cpu); 62 | break; 63 | // XOR family 64 | case 0x31: 65 | xor(cpu); 66 | break; 67 | default: 68 | cpu->errno = EUNIMPLEMENTED; 69 | break; 70 | } 71 | #ifdef DEBUG 72 | dump_state(*cpu); 73 | #endif 74 | } 75 | 76 | void dump_core(cpu_t cpu) 77 | { 78 | FILE* coredump = fopen("core.dmp","w"); 79 | //unsigned int start_address = 0; 80 | //unsigned int i = 0; 81 | if(coredump == NULL) 82 | { 83 | fprintf(stderr,"Could not open core.dmp for writing. No core dump written.\n"); 84 | return; 85 | } 86 | // binary version is more useful at the moment so we can do diffs against the floppy 87 | fwrite(cpu.memory,MEMORY_SIZE,1,coredump); 88 | /* ascii version 89 | #define BYTES_PER_LINE 16 90 | while(start_address < (MEMORY_SIZE-BYTES_PER_LINE)) 91 | { 92 | i=0; 93 | fprintf(coredump,"%.8X: ",start_address); 94 | while(i