Initial Commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
example/test
|
||||||
|
build/
|
||||||
|
build/*
|
||||||
|
.cache/
|
||||||
|
.cache/*
|
||||||
|
.gdb_history
|
||||||
21
CMakeLists.txt
Normal file
21
CMakeLists.txt
Normal 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
BIN
MOV — Move.pdf
Normal file
Binary file not shown.
5
example/test.c
Normal file
5
example/test.c
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
int main(void){
|
||||||
|
int a = 16;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
src/Bus.cpp
Normal file
10
src/Bus.cpp
Normal 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
41
src/Bus.h
Normal 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
69
src/CPU.cpp
Normal 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
33
src/CPU.h
Normal 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
104
src/ExecutorCases.cpp
Normal 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
38
src/ExecutorCases.h
Normal 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
19
src/Instruction.cpp
Normal 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
27
src/Instruction.h
Normal 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
20
src/Metal.cpp
Normal 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
23
src/Metal.h
Normal 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
14
src/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user