From 4a22467df5229c3f5419c0e17eeb333dffefcce8 Mon Sep 17 00:00:00 2001 From: Felix Kehrer Date: Wed, 7 Jul 2021 22:04:01 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 16 +++++ Cargo.toml | 9 +++ README.md | 3 + doc/Binary.md | 9 +++ doc/ISA.md | 54 ++++++++++++++++ src/main.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/echo.eis | Bin 0 -> 68 bytes 8 files changed, 268 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 doc/Binary.md create mode 100644 doc/ISA.md create mode 100644 src/main.rs create mode 100644 test/echo.eis diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8dd2c01 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "eis" +version = "0.1.0" +dependencies = [ + "byteorder", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f9c1730 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "eis" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +byteorder = "1.4.3" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..936a65e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# README + +`cargo run --release -- test/echo.eis` diff --git a/doc/Binary.md b/doc/Binary.md new file mode 100644 index 0000000..180ea48 --- /dev/null +++ b/doc/Binary.md @@ -0,0 +1,9 @@ +# Binary program format + +The current binary format is super simple, because all CPU state (registers) is part of the memory. +That means you can think of it like this: There is a header (containing the registers), and a memory block. +My suggestion is to just put the program instructions right after the header, and put stack and heap wherever you want (by setting the registers). +Don't forget to point `ip` to the start of your program! + +In the future, I might consider some sort of mapping format, but honestly, why make it complicated? +(For ASLR, I will need to define a range or something...) \ No newline at end of file diff --git a/doc/ISA.md b/doc/ISA.md new file mode 100644 index 0000000..121b20d --- /dev/null +++ b/doc/ISA.md @@ -0,0 +1,54 @@ +# ISA + +## Current impl + +| opcode | instruction | +| --- | --- | +| 0x10 | Input | +| 0x11 | Output | +| 0x20 | Mov | +| 0x90 | Nop | + +## Basics + +Input, Output, Mov + +## Math + +Add, Sub, Mov, Div (will need flags...) + +## Conditions + +Cmp, Jcc + +## Stack + +Push, Pop + +## Funcion stuff + +Call, Ret + +## x86 + +| opcode | instruction | +| --- | --- | +| 0x00 - 0x05 | ADD | +| 0x06 | PUSH | +| 0x07 | POP | +| 0x08 - 0xD | OR | +| 0x0E | PUSH | +| 0x0F | POP | +| 0x10 - 0x15 | ADC | +| 0x16 | PUSH | +| 0x17 | POP | +| 0x18 - 0x1D | SBB | +| 0x1E | PUSH | +| 0x1F | POP | +| 0x20 - 0x25 | AND | +| 0x26 | ??? | +| 0x27 | DAA | +| 0x28 - 0x2D | SUB | +| 0x2E | ??? | +| 0x2F | DAS | +| ... | ... | diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..40676e4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,176 @@ +use byteorder::{ReadBytesExt, LE}; +use std::io::Result; + +type Word = u16; +//type Memory = [Word; Word::MAX as usize + 1]; + +struct Memory { + words: [Word; Word::MAX as usize + 1], +} + +impl Memory { + fn get(&self, index: Word) -> Word { + self.words[index as usize] + } + + fn get_ref(&self, index: Word) -> Word { + // equivalent to get(get(index)) + self.words[self.words[index as usize] as usize] + } + + fn put(&mut self, index: Word, value: Word) { + self.words[index as usize] = value; + } + + //fn put_ref(&mut self, index: Word, value: Word) {} + + fn new(path_str: &str) -> Memory { + let mut file = match std::fs::File::open(path_str) { + Result::Ok(x) => x, + Result::Err(_) => panic!("could not open program"), + }; + let mut mem = Memory { + words: [0x0; u16::MAX as usize + 1], + }; + let mut index = 0; + loop { + match file.read_u16::() { + Result::Ok(word) => { + mem.words[index] = word; + //println!("DEBUG: WORD: {:#06x}", word); + index += 1; + } + Result::Err(error) => match error.kind() { + std::io::ErrorKind::UnexpectedEof => { + //println!("DEBUG: EOF"); + break; + } + other_error => panic!("Other error: {:?}", other_error), + }, + } + } + mem + } +} + +struct Cpu { + //ax: Word, // Accumulator + //bx: Word, // Base + //cx: Word, // Counter + //dx: Word, // Data + + //si: Word, // Source Index + //di: Word, // Destination Index + //bp: Word, // Base Pointer + //sp: Word, // Stack Pointer + ip: Word, // Instruction Pointer + + // Segment & Status registers ignored for now + memory: Memory, // for convenience, the CPU has ownership of the memory + + //registers are part of the memory now... + + // TODO proper input handling (probably read from command line) + input: Vec, + input_index: usize, +} + +impl Cpu { + fn _read_input(&mut self) -> Word { + // TODO length check or something... + let byte = self.input[self.input_index] as Word; + self.input_index += 1; + //println!("DEBUG: read byte {}", byte); + byte + } + fn _first_arg(&self) -> Word { + self.memory + .get(match self.memory.get(self.ip).checked_add(1) { + Some(x) => x, + None => panic!("malformed instruction at {}", self.memory.get(self.ip)), + }) + } + + fn _second_arg(&self) -> Word { + self.memory + .get(match self.memory.get(self.ip).checked_add(2) { + Some(x) => x, + None => panic!("malformed instruction at {}", self.memory.get(self.ip)), + }) + } + + fn step(&mut self) { + //println!("DEBUG: IP: {}", self.memory.get(self.ip)); + // fetch and decode instruction + let (op, len) = match self.memory.get_ref(self.ip) { + 0x10 => (Op::Input(self._first_arg()), 2), + 0x11 => (Op::Output(self._first_arg()), 2), + 0x20 => (Op::Mov(self._first_arg(), self._second_arg()), 3), + 0x90 => (Op::Nop, 1), + 0xFF => (Op::Exit(self._first_arg()), 2), + x => panic!("unknown instruction {} at {}", x, self.memory.get(self.ip)), + }; + + // increment ip + self.memory + .put(self.ip, self.memory.get(self.ip).wrapping_add(len)); + + // execute instruction + match op { + Op::Input(address) => { + //println!("DEBUG: INPUT: {}", address); + let x = self._read_input(); + self.memory.put(address, x); + } + Op::Mov(dest, src) => self.memory.put(dest, self.memory.get(src)), + Op::Nop => {} + Op::Output(address) => { + //println!("DEBUG: OUTPUT: {}", address); + print!("{} ", self.memory.get(address)); + } + Op::Exit(status) => std::process::exit(status as i32), + } + } + + fn new(memory: Memory) -> Cpu { + // TODO number registers, leave 0 for NULL + // TODO either put at end of address space + // or offset programs + Cpu { + ip: 9, + memory, + input: vec![54, 65, 73, 74], // TODO proper input from command line or stdin + input_index: 0, + } + } +} + +enum Op { + Input(Word), + Mov(Word, Word), + Nop, + Output(Word), + Exit(Word), +} + +/* +impl Op { + fn length(&self) -> Word { + match *self { + Op::Nop => 1, + Op::Input(_) | Op::Output(_) => 2, + Op::Mov(_, _) => 3, + } + } +}*/ + +fn main() { + let args: Vec = std::env::args().collect(); + let memory = Memory::new(&args[1]); + let mut cpu = Cpu::new(memory); + + for _ in 0..=255 { + cpu.step(); + } + println!(); +} diff --git a/test/echo.eis b/test/echo.eis new file mode 100644 index 0000000000000000000000000000000000000000..443578742fa52910098aa1da9ec76c7693dd757a GIT binary patch literal 68 ocmZQzKnDUS0zjUC00Scs3jnd8fFO`B2;>U_`GP?He+CmF03g}|o&W#< literal 0 HcmV?d00001