commit 4671a62ddfdc574c923d0fd73258bdf8909a11c9 Author: 0x221E Date: Wed Feb 4 12:52:42 2026 +0100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d021de --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +example/test +build/ +build/* +.cache/ +.cache/* +.gdb_history \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f694ae7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) +project(emulator CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(Debug) + set(CMAKE_BUILD_TYPE Debug) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -g") + message(STATUS "Building with debug mode.") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector") +endif() + +file(GLOB_RECURSE SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_executable(emulator ${SRC_FILES}) diff --git a/MOV — Move.pdf b/MOV — Move.pdf new file mode 100644 index 0000000..1425c7c Binary files /dev/null and b/MOV — Move.pdf differ diff --git a/example/test.c b/example/test.c new file mode 100644 index 0000000..1a3bcb2 --- /dev/null +++ b/example/test.c @@ -0,0 +1,5 @@ + +int main(void){ + int a = 16; + return 0; +} diff --git a/src/Bus.cpp b/src/Bus.cpp new file mode 100644 index 0000000..60e9780 --- /dev/null +++ b/src/Bus.cpp @@ -0,0 +1,10 @@ +#include "Bus.h" + +#include + +Bus::Bus() { + for(int i = 0; i < 992 * 1024; i++) + { + m_RAM[i] = 0; + } +} diff --git a/src/Bus.h b/src/Bus.h new file mode 100644 index 0000000..7300141 --- /dev/null +++ b/src/Bus.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +class Bus { +public: + Bus(); + ~Bus() = default; + +public: + template + void WriteX(uint64_t address, T value) + { + static_assert(std::is_unsigned_v, "T must be an unsigned int of any size smaller than 8 bytes!"); + T& loc = AccessX(address); + loc = value; + } + + template + T& AccessX(uint64_t address) + { + static_assert(std::is_unsigned_v, "T must be an unsigned int of any size smaller than 8 bytes!"); + + std::cout << "Bus access: " << std::hex << address << std::endl; + + if (address >= 0x00008000 && address <= 0x000FFFFF) + { + uint64_t offset = address - 0x00008000; + T* entry = reinterpret_cast(&m_RAM[offset]); + return entry[0]; + } + + throw std::runtime_error("Illegal Access!"); + } + +private: + uint8_t m_RAM[992 * 1024]; +}; diff --git a/src/CPU.cpp b/src/CPU.cpp new file mode 100644 index 0000000..09990f4 --- /dev/null +++ b/src/CPU.cpp @@ -0,0 +1,69 @@ +#include "CPU.h" + +#include +#include +#include +#include +#include + +CPU::CPU(std::shared_ptr bus) : m_Bus(bus), m_Context({m_Instruction, m_InstructionPointer, m_Flags, m_Registers, m_Bus}){ + m_InstructionPointer = 0x00008000; + + for(int i = 0; i < 16; i++) + { + m_Registers[i] = 0; + } +} + +void CPU::Step(){ + FetchDecode(); + Execute(); +} + +void CPU::FetchDecode(){ + std::cout << "Fetching instruction: " << std::hex << m_InstructionPointer << std::endl; + m_InstructionRaw = m_Bus->AccessX(m_InstructionPointer); // Slice of 8 bytes. + + std::cout << "Context window fetched: " << std::hex << m_InstructionRaw << std::endl; // Start Decode Instruction + uint64_t first_byte = m_InstructionRaw & 0xFF; + std::cout << "Decoded first byte: " << std::hex << first_byte << std::endl; + Opcode opcode = static_cast(first_byte); + + if(first_byte >= 0xB8 && first_byte <= 0xBF) + { + m_Instruction.m_Operand1 = first_byte - 0xB8; + opcode = Opcode::MOV_R_IMM32; + } + + switch(opcode){ + case Opcode::NOP: m_Instruction.m_Length = 1; break; + case Opcode::HLT: m_Instruction.m_Length = 1; break; + case Opcode::MOV_R_IMM32: + m_Instruction.m_Operand2 = m_Bus->AccessX(m_InstructionPointer + 1); + m_Instruction.m_Length = 5; + break; + case Opcode::ADD_RM32_R32: + m_Instruction.m_Operand1 = m_Bus->AccessX(m_InstructionPointer + 1); + m_Instruction.m_Operand2 = m_Bus->AccessX(m_InstructionPointer + 2); + m_Instruction.m_Length = 6; + break; + default: + std::runtime_error("Decode encountered unexpected opcode."); + break; + } + + m_Instruction.m_Opcode = opcode; + m_InstructionPointer += m_Instruction.m_Length; +} + +void CPU::Execute(){ + std::cout << "Executing... \n"; + uint8_t opcode_value = static_cast(m_Instruction.m_Opcode); + auto& exec_table = GetExecutorTable(); + if(exec_table[opcode_value]) + { + exec_table[opcode_value](m_Context); + return; + } + throw std::runtime_error("Opcode not found!"); +} diff --git a/src/CPU.h b/src/CPU.h new file mode 100644 index 0000000..e01a36b --- /dev/null +++ b/src/CPU.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "Instruction.h" +#include "Bus.h" +#include "ExecutorCases.h" + +class CPU { +public: + CPU(std::shared_ptr bus); + ~CPU() = default; + +public: + void Step(); + +private: + void FetchDecode(); + void Execute(); + +private: + uint64_t m_Registers[16]; + uint64_t m_InstructionPointer; + uint64_t m_Flags; + + std::shared_ptr m_Bus; + + uint64_t m_InstructionRaw; + Instruction m_Instruction; + + CPUContext m_Context; +}; diff --git a/src/ExecutorCases.cpp b/src/ExecutorCases.cpp new file mode 100644 index 0000000..f05d330 --- /dev/null +++ b/src/ExecutorCases.cpp @@ -0,0 +1,104 @@ +#include "ExecutorCases.h" + +#include "Instruction.h" +#include "Bus.h" + +#include +#include + +CPUContext::CPUContext(Instruction& i, uint64_t& ip, uint64_t& flags, uint64_t* reg, std::shared_ptr& bus) : m_Instruction(i), m_InstructionPointer(ip), m_Flags(flags), m_Registers(reg), m_Bus(bus) { } + +CPUContext::~CPUContext() = default; + +// NO SIB SUPPORT YET +ModRM process_modrm(uint8_t modrm){ + uint8_t mod_mask = 0b11000000; + uint8_t reg_mask = 0b00111000; + uint8_t rm_mask = 0b00000111; + + uint8_t mod = modrm & mod_mask; + uint8_t reg = (modrm & reg_mask) >> 3; + uint8_t rm = modrm & rm_mask; + + ModRMState state = ModRMState::INVALID; + + switch(mod) { + case 0b00000000: + state = ModRMState::LR; + break; + case 0b01000000: + state = ModRMState::LR_DISP8; + break; + case 0b10000000: + state = ModRMState::LR_DISP32; + break; + case 0b11000000: + state = ModRMState::R; + break; + default: + throw std::runtime_error("Mod R/M does not support non-register operands right now!"); + } + return {.m_State = state, .m_Reg = reg, .m_Rm = rm}; +} + +namespace executor_cases { + void Nop(CPUContext& cc){ + std::cout << "No op" << std::endl; + } + + void Hlt(CPUContext& cc){ + std::cout << "Program halted!" << std::endl; + exit(0); + } + + void Mov_r32_imm32(CPUContext& cc){ + cc.m_Registers[cc.m_Instruction.m_Operand1] = cc.m_Instruction.m_Operand2; + std::cout << "Contents of " << x86::Register2Str((x86::Register)cc.m_Instruction.m_Operand1) << " changed to " << cc.m_Registers[cc.m_Instruction.m_Operand1] << std::endl; + } + + //NO SIB SUPPORT YET + void Add_rm32_r32(CPUContext& cc){ + ModRM modrm = process_modrm(cc.m_Instruction.m_Operand1); + + switch(modrm.m_State) { + case ModRMState::R: + { + cc.m_Registers[modrm.m_Rm] += cc.m_Registers[modrm.m_Reg]; + std::cout << "Adding " << x86::Register2Str((x86::Register)modrm.m_Reg) << " to " << x86::Register2Str((x86::Register)modrm.m_Rm) << std::endl; + break; + } + case ModRMState::LR: + { + uint32_t dstPrevValue = cc.m_Bus->AccessX(cc.m_Registers[modrm.m_Rm]); + uint32_t currRegValue = cc.m_Registers[modrm.m_Reg]; + uint32_t result = dstPrevValue + currRegValue; + cc.m_Bus->WriteX(cc.m_Registers[modrm.m_Rm], result); + std::cout << "Memory address " << std::hex << cc.m_Registers[modrm.m_Rm] << " modified to: " << result << std::endl; + break; + } + case ModRMState::LR_DISP8: + { + + } + default: + { + throw std::runtime_error("Invalid ModRM State encountered during Add_rm32_r32"); + } + } + } +} + +constexpr std::array GenerateExecutorTable(){ + std::array table{}; + table[Opcode::NOP] = executor_cases::Nop; + table[Opcode::HLT] = executor_cases::Hlt; + table[Opcode::MOV_R_IMM32] = executor_cases::Mov_r32_imm32; + table[Opcode::ADD_RM32_R32] = executor_cases::Add_rm32_r32; + return table; +} + +static constexpr std::array s_ExecutorTable = GenerateExecutorTable(); + +const std::array& GetExecutorTable() { + return s_ExecutorTable; +} diff --git a/src/ExecutorCases.h b/src/ExecutorCases.h new file mode 100644 index 0000000..37b4b3e --- /dev/null +++ b/src/ExecutorCases.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +struct Instruction; +class Bus; + +struct CPUContext { + Instruction& m_Instruction; + uint64_t& m_InstructionPointer; + uint64_t& m_Flags; + uint64_t* m_Registers; + std::shared_ptr m_Bus; + + CPUContext(Instruction& i, uint64_t& ip, uint64_t& flags, uint64_t* reg, std::shared_ptr& bus); + ~CPUContext(); +}; + +enum class ModRMState : uint8_t +{ + INVALID = 0, + LR = 1, + LR_DISP8 = 2, + LR_DISP32 = 3, + R = 4 +}; + +struct ModRM{ + ModRMState m_State; + uint8_t m_Reg; + uint8_t m_Rm; +}; + +typedef void (*ExecutorCase)(CPUContext&); + +const std::array& GetExecutorTable(); diff --git a/src/Instruction.cpp b/src/Instruction.cpp new file mode 100644 index 0000000..7c7d9c0 --- /dev/null +++ b/src/Instruction.cpp @@ -0,0 +1,19 @@ +#include "Instruction.h" + +#include + +namespace x86 { + std::string Register2Str(x86::Register reg) { + switch(reg){ + case x86::Register::EAX: return "EAX"; + case x86::Register::ECX: return "ECX"; + case x86::Register::EDX: return "EDX"; + case x86::Register::EBX: return "EBX"; + case x86::Register::ESP: return "ESP"; + case x86::Register::EBP: return "EBP"; + case x86::Register::ESI: return "ESI"; + case x86::Register::EDI: return "EDI"; + } + throw std::runtime_error("Register not found!"); + } +} diff --git a/src/Instruction.h b/src/Instruction.h new file mode 100644 index 0000000..cf4fbdc --- /dev/null +++ b/src/Instruction.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace x86 { + enum Register : uint8_t { + EAX = 0, ECX = 1, EDX = 2, EBX = 3, + ESP = 4, EBP = 5, ESI = 6, EDI = 7 + }; + + std::string Register2Str(x86::Register reg); +} + +enum Opcode : uint8_t { + NOP = 0x90, + HLT = 0xF4, + MOV_R_IMM32 = 0xB8, + ADD_RM32_R32 = 0x01, +}; + +struct Instruction{ + Opcode m_Opcode; + size_t m_Length; + uint64_t m_Operand1; + uint64_t m_Operand2; +}; diff --git a/src/Metal.cpp b/src/Metal.cpp new file mode 100644 index 0000000..229c1e8 --- /dev/null +++ b/src/Metal.cpp @@ -0,0 +1,20 @@ +#include "Metal.h" + +#include "Bus.h" + +Metal::Metal() : m_Bus(std::make_shared()), m_CPU(m_Bus) { } + +void Metal::Upload2Memory(uint8_t bytes[], size_t len) { + uint64_t start = 0x8000; + for (size_t i = 0; i < len; i++) { + m_Bus->WriteX(start + i, bytes[i]); + std::cout << "Written " << bytes[i] << " to " << std::hex << start + i << std::endl; + } +} + +void Metal::Run() { + m_Running = true; + while(m_Running) { + m_CPU.Step(); + } +} diff --git a/src/Metal.h b/src/Metal.h new file mode 100644 index 0000000..e5405fa --- /dev/null +++ b/src/Metal.h @@ -0,0 +1,23 @@ +#pragma once + +class Bus; + +#include +#include + +#include "CPU.h" + +class Metal { + public: + Metal(); + ~Metal() = default; + + void Upload2Memory(uint8_t bytes[], size_t len); + + void Run(); + + private: + std::shared_ptr m_Bus; + CPU m_CPU; + bool m_Running = false; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..537eea4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,14 @@ +#include + +#include "Metal.h" + +uint8_t test[] = { + 0x90, 0xF4 +}; + +int main(int argc, char** argv) { + Metal metal; + metal.Upload2Memory(test, 2); + metal.Run(); + return 0; +}