Initial Commit

This commit is contained in:
0x221E
2026-02-04 12:52:42 +01:00
commit 4671a62ddf
15 changed files with 430 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
example/test
build/
build/*
.cache/
.cache/*
.gdb_history

21
CMakeLists.txt Normal file
View File

@@ -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})

BIN
MOV — Move.pdf Normal file

Binary file not shown.

5
example/test.c Normal file
View File

@@ -0,0 +1,5 @@
int main(void){
int a = 16;
return 0;
}

10
src/Bus.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "Bus.h"
#include <stdexcept>
Bus::Bus() {
for(int i = 0; i < 992 * 1024; i++)
{
m_RAM[i] = 0;
}
}

41
src/Bus.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <iostream>
#include <cstdint>
#include <type_traits>
#include <stdexcept>
class Bus {
public:
Bus();
~Bus() = default;
public:
template<typename T>
void WriteX(uint64_t address, T value)
{
static_assert(std::is_unsigned_v<T>, "T must be an unsigned int of any size smaller than 8 bytes!");
T& loc = AccessX<T>(address);
loc = value;
}
template<typename T>
T& AccessX(uint64_t address)
{
static_assert(std::is_unsigned_v<T>, "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<T*>(&m_RAM[offset]);
return entry[0];
}
throw std::runtime_error("Illegal Access!");
}
private:
uint8_t m_RAM[992 * 1024];
};

69
src/CPU.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "CPU.h"
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <array>
CPU::CPU(std::shared_ptr<Bus> 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<uint64_t>(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<Opcode>(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<uint32_t>(m_InstructionPointer + 1);
m_Instruction.m_Length = 5;
break;
case Opcode::ADD_RM32_R32:
m_Instruction.m_Operand1 = m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1);
m_Instruction.m_Operand2 = m_Bus->AccessX<uint32_t>(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<uint8_t>(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!");
}

33
src/CPU.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include <memory>
#include "Instruction.h"
#include "Bus.h"
#include "ExecutorCases.h"
class CPU {
public:
CPU(std::shared_ptr<Bus> 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<Bus> m_Bus;
uint64_t m_InstructionRaw;
Instruction m_Instruction;
CPUContext m_Context;
};

104
src/ExecutorCases.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "ExecutorCases.h"
#include "Instruction.h"
#include "Bus.h"
#include <bitset>
#include <iostream>
CPUContext::CPUContext(Instruction& i, uint64_t& ip, uint64_t& flags, uint64_t* reg, std::shared_ptr<Bus>& 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<uint32_t>(cc.m_Registers[modrm.m_Rm]);
uint32_t currRegValue = cc.m_Registers[modrm.m_Reg];
uint32_t result = dstPrevValue + currRegValue;
cc.m_Bus->WriteX<uint32_t>(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<ExecutorCase, 255> GenerateExecutorTable(){
std::array<ExecutorCase, 255> 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<ExecutorCase, 255> s_ExecutorTable = GenerateExecutorTable();
const std::array<ExecutorCase, 255>& GetExecutorTable() {
return s_ExecutorTable;
}

38
src/ExecutorCases.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <memory>
#include <array>
struct Instruction;
class Bus;
struct CPUContext {
Instruction& m_Instruction;
uint64_t& m_InstructionPointer;
uint64_t& m_Flags;
uint64_t* m_Registers;
std::shared_ptr<Bus> m_Bus;
CPUContext(Instruction& i, uint64_t& ip, uint64_t& flags, uint64_t* reg, std::shared_ptr<Bus>& 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<ExecutorCase, 255>& GetExecutorTable();

19
src/Instruction.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include "Instruction.h"
#include <stdexcept>
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!");
}
}

27
src/Instruction.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <cstdint>
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;
};

20
src/Metal.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "Metal.h"
#include "Bus.h"
Metal::Metal() : m_Bus(std::make_shared<Bus>()), 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();
}
}

23
src/Metal.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
class Bus;
#include <cstdint>
#include <memory>
#include "CPU.h"
class Metal {
public:
Metal();
~Metal() = default;
void Upload2Memory(uint8_t bytes[], size_t len);
void Run();
private:
std::shared_ptr<Bus> m_Bus;
CPU m_CPU;
bool m_Running = false;
};

14
src/main.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include <iostream>
#include "Metal.h"
uint8_t test[] = {
0x90, 0xF4
};
int main(int argc, char** argv) {
Metal metal;
metal.Upload2Memory(test, 2);
metal.Run();
return 0;
}