diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Arithmetic.cpp | 63 | ||||
| -rw-r--r-- | src/Arithmetic.h | 14 | ||||
| -rw-r--r-- | src/Bus.cpp | 7 | ||||
| -rw-r--r-- | src/Bus.h | 65 | ||||
| -rw-r--r-- | src/CPU.cpp | 142 | ||||
| -rw-r--r-- | src/CPU.h | 57 | ||||
| -rw-r--r-- | src/CPUContext.cpp | 5 | ||||
| -rw-r--r-- | src/CPUContext.h | 19 | ||||
| -rw-r--r-- | src/ControlFlow.cpp | 13 | ||||
| -rw-r--r-- | src/ControlFlow.h | 7 | ||||
| -rw-r--r-- | src/DataTransfer.cpp | 30 | ||||
| -rw-r--r-- | src/DataTransfer.h | 21 | ||||
| -rw-r--r-- | src/Exceptions.h | 14 | ||||
| -rw-r--r-- | src/ExecutorCases.cpp | 97 | ||||
| -rw-r--r-- | src/ExecutorCases.h | 33 | ||||
| -rw-r--r-- | src/GUI.cpp | 194 | ||||
| -rw-r--r-- | src/GUI.h | 27 | ||||
| -rw-r--r-- | src/Instruction.cpp | 49 | ||||
| -rw-r--r-- | src/Instruction.h | 110 | ||||
| -rw-r--r-- | src/InstructionModifierLookup.cpp | 76 | ||||
| -rw-r--r-- | src/InstructionModifierLookup.h | 11 | ||||
| -rw-r--r-- | src/Misc.cpp | 12 | ||||
| -rw-r--r-- | src/Misc.h | 7 | ||||
| -rw-r--r-- | src/RAM.cpp | 12 | ||||
| -rw-r--r-- | src/RAM.h | 14 | ||||
| -rw-r--r-- | src/Userspace.cpp | 99 | ||||
| -rw-r--r-- | src/Userspace.h | 52 | ||||
| -rw-r--r-- | src/main.cpp | 35 |
28 files changed, 1285 insertions, 0 deletions
diff --git a/src/Arithmetic.cpp b/src/Arithmetic.cpp new file mode 100644 index 0000000..dd2a1ef --- /dev/null +++ b/src/Arithmetic.cpp @@ -0,0 +1,63 @@ +#include "Arithmetic.h" + +#include "ExecutorCases.h" +#include "CPUContext.h" +#include "Instruction.h" +#include "Bus.h" + +#include <stdexcept> +#include <iostream> + +namespace executor_cases { + void Add_rm32_r32(CPUContext& cc){ + x86::ModRM modrm = cc.m_Instruction.m_ModRM; + + std::cout << "[Instruction] "; + + switch(modrm.m_State) { + case x86::ModRMState::R: + cc.m_Registers[modrm.m_Rm] += cc.m_Registers[modrm.m_Reg]; + std::cout << "add " << x86::Register2Str((x86::Register)modrm.m_Rm) << ", " << x86::Register2Str((x86::Register)modrm.m_Rm); + break; + default: + helpers::Add_rm_dst(cc, helpers::ResolveModRMAddress(cc)); + std::cout << "add DWORD PTR [0x" << helpers::ResolveModRMAddress(cc) << "], " << x86::Register2Str((x86::Register)modrm.m_Reg); + break; + } + + std::cout << std::endl; + } + + // TODO: Finish the function + void Add_r32_rm32(CPUContext& cc){ + // x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM; + + // std::cout << "[Instruction] "; + + // switch(modrm.m_State) { + // case x86::ModRMState::R: + // cc.m_Registers[modrm.m_Reg] += cc.m_Registers[modrm.m_Rm]; + // std::cout << "add " << x86::Register2Str((x86::Register)modrm.m_Reg) << ", " << x86::Register2Str((x86::Register)modrm.m_Rm); + // break; + // default: + // uint32_t dstAddress = ;ResolveModRMAddress(cc); + // uint32_t dstPrevValue = cc.m_Bus->AccessX<uint32_t>(dstAddress); + // uint32_t currRegValue = cc.m_Registers[cc.m_Instruction.optional.m_ModRM.m_Reg]; + // uint32_t result = dstPrevValue + currRegValue; + // std::cout << "add DWORD PTR [0x" << helpers::ResolveModRMAddress(cc) << "], " << x86::Register2Str((x86::Register)modrm.m_Reg); + // break; + // } + + // std::cout << std::endl; + std::runtime_error("Not implemented!"); + } + + namespace helpers { + void Add_rm_dst(CPUContext& cc, uint32_t address) { + uint32_t dstPrevValue = cc.m_Bus->AccessX<uint32_t>(address); + uint32_t currRegValue = cc.m_Registers[cc.m_Instruction.m_ModRM.m_Reg]; + uint32_t result = dstPrevValue + currRegValue; + cc.m_Bus->WriteX<uint32_t>(address, result); + } + } +} diff --git a/src/Arithmetic.h b/src/Arithmetic.h new file mode 100644 index 0000000..fd80fc0 --- /dev/null +++ b/src/Arithmetic.h @@ -0,0 +1,14 @@ +#pragma once + +#include <cstdint> + +struct CPUContext; + +namespace executor_cases { + void Add_rm32_r32(CPUContext& cc); + void Add_r32_rm32(CPUContext& cc); //TODO: Finish + + namespace helpers { + void Add_rm_dst(CPUContext& cc, uint32_t address); + } +} diff --git a/src/Bus.cpp b/src/Bus.cpp new file mode 100644 index 0000000..ad035ce --- /dev/null +++ b/src/Bus.cpp @@ -0,0 +1,7 @@ +#include "Bus.h" + +#include <stdexcept> + +Bus::Bus(std::shared_ptr<RAM> ram) : m_RAM(ram) { + +} diff --git a/src/Bus.h b/src/Bus.h new file mode 100644 index 0000000..d55ee4f --- /dev/null +++ b/src/Bus.h @@ -0,0 +1,65 @@ +#pragma once + +#include <iostream> +#include <cstdint> +#include <type_traits> +#include <stdexcept> +#include <cstring> +#include <memory> + +#include "RAM.h" + +#include "Exceptions.h" + +class Bus { +public: + Bus(std::shared_ptr<RAM> m_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!"); + + // std::cout << "Bus write: " << std::hex << address << std::endl; + + switch(address) + { + case 0x00008000 ... 0x000FFFFF: + { + uint64_t offset = address - 0x00008000; + std::memcpy(&m_RAM->Data()[offset], &value, sizeof(T)); + break; + } + default: + std::string exception = "Illegal access to: " + std::to_string(address); + throw CPUException(exception); + } + } + + 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; + + switch(address) + { + case 0x00008000 ... 0x000FFFFF: + { + uint64_t offset = address - 0x00008000; + T value; + std::memcpy(&value, &m_RAM->Data()[offset], sizeof(T)); + return value; + } + default: + std::string exception = "Illegal access to: " + std::to_string(address); + throw std::runtime_error(exception); + } + } + +private: + std::shared_ptr<RAM> m_RAM; +}; diff --git a/src/CPU.cpp b/src/CPU.cpp new file mode 100644 index 0000000..dd03a19 --- /dev/null +++ b/src/CPU.cpp @@ -0,0 +1,142 @@ +#include "CPU.h" + +#include <stdexcept> +#include <iostream> +#include <iomanip> +#include <bitset> +#include <stdlib.h> +#include <array> +#include <cassert> + +#include "Exceptions.h" +#include "InstructionModifierLookup.h" + +static const Instruction s_EmptyInstruction{}; + +CPU::CPU(std::shared_ptr<Bus>& bus) : m_Bus(bus), m_IsHalted(false), m_Context({m_Instruction, m_InstructionPointer, m_Flags, m_Registers, m_Bus, m_IsHalted}) { + m_InstructionPointer = 0x00008000; + + for(int i = 0; i < 8; i++) { + m_Registers[i] = 0; + } + + for(int i = 0; i < 8; i++) { + m_SegmentRegisters[i] = 0; + } +} + +void CPU::Step() { + FetchDecode(); + Execute(); +} + +void CPU::Dump() { + std::cout << "--TRACE-- "; + std::cout << std::endl; + + for(uint8_t i = 0; i < 8; i++) + { + std::cout << x86::Register2Str((x86::Register)i) << ": " << m_Registers[i] << " | "; + } + + std::cout << std::endl; + + std::bitset<32> flags(m_Flags); + std::cout << "IP: " << std::hex << m_InstructionPointer << " | FLAGS: " << flags; + + std::cout << std::endl; +} + +void CPU::Reset() { + for(uint8_t i = 0; i < 8; i++) + { + m_Registers[i] = 0; + m_SegmentRegisters[i] = 0; + } + + m_InstructionPointer = 0x8000; + m_Flags = 0; + + std::cout << "[CPU] State Flushed!" << std::endl; + m_IsHalted = false; + m_Instruction = s_EmptyInstruction; +} + +CPUStatus CPU::GetStatus() { + return (CPUStatus){.m_Registers = m_Registers, .m_IP = m_InstructionPointer, .m_Instruction = m_Instruction}; +} + +void CPU::FetchDecode() { + m_Instruction = s_EmptyInstruction; + + uint8_t opcode_raw = m_Bus->AccessX<uint8_t>(m_InstructionPointer); + Opcode opcode = static_cast<Opcode>(opcode_raw); + + auto& instruction_table = GetInstructionTable(); + if(instruction_table[opcode_raw].m_Executor == nullptr) { + std::cout << "Encoding: " << opcode_raw << std::endl; + std::string exception = "[CPU] [FetchDecode] Opcode is not in instruction table! Opcode_raw: " + std::to_string(opcode_raw); + throw CPUException(exception); + } + + auto& instruction = instruction_table[opcode_raw]; + + uint8_t encoding_mask = 0b00001111; + uint8_t encoding = instruction.m_Encoding & encoding_mask; + uint8_t immediate = instruction.m_Encoding & ~encoding_mask; + + m_Instruction.m_Opcode = opcode; + + switch(encoding) { + case OI: + if (immediate == I32) { + m_Instruction.m_Operand1 = m_Bus->AccessX<uint32_t>(m_InstructionPointer + 1); + m_Instruction.m_Length = 5; + } else if (immediate == I16) { + m_Instruction.m_Operand1 = m_Bus->AccessX<uint16_t>(m_InstructionPointer + 1); + m_Instruction.m_Length = 3; + } else if (immediate == I8) { + m_Instruction.m_Operand1 = m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1); + m_Instruction.m_Length = 2; + } else { + throw CPUException("[CPU] [FetchDecode] OI instruction encoding only supports imm8,imm16,imm32."); + } + break; + case ZO: + m_Instruction.m_Length = 1; + break; + case RM: + case MR: { + uint8_t modrm = m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1); + m_Instruction.m_ModRM = x86::ProcessMODRM(modrm); + m_Instruction.m_Length = 2; + FetchModRMFields(modrm); + if (m_Instruction.m_ModRM.m_SIB) { + uint8_t sib = m_Bus->AccessX<uint8_t>(m_InstructionPointer + m_Instruction.m_Length); + m_Instruction.m_Length += 1; + m_Instruction.m_SIB = x86::ProcessSIB(sib); + } + break; + } + default: + throw CPUException("[CPU] [FetchDecode] Encoding was not found!"); + } + + m_Instruction.m_Func = instruction.m_Executor; + m_InstructionPointer += m_Instruction.m_Length; +} + +void CPU::Execute() { + if(m_Instruction.m_Func != nullptr) + { + m_Instruction.m_Func(m_Context); + return; + } + throw std::runtime_error("[CPU] [Execute] m_Func is nullptr!"); +} + +void CPU::FetchModRMFields(uint8_t modrm) { + assert(m_Instruction.m_Length != 0); // FetchDecode() must set m_Length before calling FetchModRMFields() + auto modrm_table = GetMod32Table(); + modrm_table[modrm](m_Context); +} diff --git a/src/CPU.h b/src/CPU.h new file mode 100644 index 0000000..1ad96aa --- /dev/null +++ b/src/CPU.h @@ -0,0 +1,57 @@ +#pragma once + +#include <cstdint> +#include <memory> + +#include "Instruction.h" +#include "Bus.h" +#include "ExecutorCases.h" +#include "CPUContext.h" + +enum class CPUExecutionMode { + BIT_32, +}; + +struct CPUStatus { + uint32_t* m_Registers; + uint32_t& m_IP; + Instruction& m_Instruction; +}; + +class CPU { +public: + CPU(std::shared_ptr<Bus>& bus); + ~CPU() = default; + +public: + void Step(); + + void Dump(); + void Reset(); + + bool IsHalted() { return m_IsHalted; } + + CPUStatus GetStatus(); + +private: + void FetchDecode(); + void Execute(); + +private: + CPUExecutionMode m_Mode; + uint32_t m_Registers[8]; + uint16_t m_SegmentRegisters[8]; + uint32_t m_InstructionPointer; + uint32_t m_Flags; + bool m_IsHalted; + + std::shared_ptr<Bus> m_Bus; + + Instruction m_Instruction; + + CPUContext m_Context; +private: + // FetchDecode() must set m_Length before calling FetchModRMFields() + void FetchModRMFields(uint8_t modrm); + void FetchSIB(); +}; diff --git a/src/CPUContext.cpp b/src/CPUContext.cpp new file mode 100644 index 0000000..e91d581 --- /dev/null +++ b/src/CPUContext.cpp @@ -0,0 +1,5 @@ +#include "CPUContext.h" + +CPUContext::CPUContext(Instruction& i, uint32_t& ip, uint32_t& flags, uint32_t* reg, std::shared_ptr<Bus>& bus, bool& isHalted) : m_Instruction(i), m_InstructionPointer(ip), m_Flags(flags), m_Registers(reg), m_Bus(bus), m_IsHalted(isHalted) { } + +CPUContext::~CPUContext() = default; diff --git a/src/CPUContext.h b/src/CPUContext.h new file mode 100644 index 0000000..a1402e6 --- /dev/null +++ b/src/CPUContext.h @@ -0,0 +1,19 @@ +#pragma once + +struct Instruction; +struct Bus; + +#include <cstdint> +#include <memory> + +struct CPUContext { + Instruction& m_Instruction; + uint32_t& m_InstructionPointer; + uint32_t& m_Flags; + uint32_t* m_Registers; + std::shared_ptr<Bus> m_Bus; + bool& m_IsHalted; + + CPUContext(Instruction& i, uint32_t& ip, uint32_t& flags, uint32_t* reg, std::shared_ptr<Bus>& bus, bool& isHalted); + ~CPUContext(); +}; diff --git a/src/ControlFlow.cpp b/src/ControlFlow.cpp new file mode 100644 index 0000000..52465ae --- /dev/null +++ b/src/ControlFlow.cpp @@ -0,0 +1,13 @@ +#include "ControlFlow.h" + +#include "ExecutorCases.h" +#include "CPUContext.h" + +#include <iostream> + +namespace executor_cases { + void Hlt(CPUContext& cc){ + std::cout << "[Instruction] hlt" << std::endl; + cc.m_IsHalted = true; + } +} diff --git a/src/ControlFlow.h b/src/ControlFlow.h new file mode 100644 index 0000000..0f8aa87 --- /dev/null +++ b/src/ControlFlow.h @@ -0,0 +1,7 @@ +#pragma once + +struct CPUContext; + +namespace executor_cases { + void Hlt(CPUContext& cc); +} diff --git a/src/DataTransfer.cpp b/src/DataTransfer.cpp new file mode 100644 index 0000000..f7cc063 --- /dev/null +++ b/src/DataTransfer.cpp @@ -0,0 +1,30 @@ +#include "DataTransfer.h" + +#include "ExecutorCases.h" +#include "Instruction.h" +#include "CPUContext.h" +#include "Bus.h" + +#include <iostream> + +namespace executor_cases { + + void Mov_rm32_r32(CPUContext& cc) { + x86::ModRM modrm = cc.m_Instruction.m_ModRM; + + std::cout << "[Instruction] "; + + switch(modrm.m_State) { + case x86::ModRMState::R: + cc.m_Registers[modrm.m_Rm] = cc.m_Registers[modrm.m_Reg]; + std::cout << "mov " << x86::Register2Str((x86::Register)modrm.m_Rm) << ", " << x86::Register2Str((x86::Register)modrm.m_Rm); + break; + default: + cc.m_Bus->WriteX<uint32_t>(helpers::ResolveModRMAddress(cc), cc.m_Registers[modrm.m_Reg]); + std::cout << "mov DWORD PTR [0x" << helpers::ResolveModRMAddress(cc) << "], " << x86::Register2Str((x86::Register)modrm.m_Reg); + break; + } + + std::cout << std::endl; + } +} diff --git a/src/DataTransfer.h b/src/DataTransfer.h new file mode 100644 index 0000000..a8ee9a1 --- /dev/null +++ b/src/DataTransfer.h @@ -0,0 +1,21 @@ +#pragma once + +#include <type_traits> +#include <cstdint> +#include <iostream> +#include <cstring> + +#include "CPUContext.h" +#include "Instruction.h" + +namespace executor_cases { + template<typename T, int O> + void Mov_rX_immX(CPUContext& cc) { + static_assert(std::is_unsigned_v<T>, "Mov_rX_immX requires an unsigned type!"); + uint8_t reg = cc.m_Instruction.m_Opcode - O; + std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)reg) << ", " << std::hex << cc.m_Instruction.m_Operand1 << std::endl; + std::memcpy(&cc.m_Registers[reg], &cc.m_Instruction.m_Operand1, sizeof(T)); + } + + void Mov_rm32_r32(CPUContext& cc); +} diff --git a/src/Exceptions.h b/src/Exceptions.h new file mode 100644 index 0000000..4378b8d --- /dev/null +++ b/src/Exceptions.h @@ -0,0 +1,14 @@ +#pragma once + +#include <stdexcept> + +class CPUException : std::runtime_error { +public: + CPUException(std::string what) : std::runtime_error(what), m_What(what) { } + ~CPUException() = default; + + std::string GetMessage() { return m_What; } + +private: + std::string m_What; +}; diff --git a/src/ExecutorCases.cpp b/src/ExecutorCases.cpp new file mode 100644 index 0000000..34587be --- /dev/null +++ b/src/ExecutorCases.cpp @@ -0,0 +1,97 @@ +#include "ExecutorCases.h" + +#include "DataTransfer.h" +#include "ControlFlow.h" +#include "Arithmetic.h" +#include "Misc.h" + +#include "Instruction.h" +#include "CPUContext.h" +#include "Bus.h" + +#include <algorithm> +#include <bitset> +#include <iostream> +#include <utility> + +constexpr std::array<InstructionEntry, 256> GenerateInstructionTable(){ + std::array<InstructionEntry, 256> table{}; + + for(uint8_t i = 0; i < 255; i++) { + table[i] = (InstructionEntry){nullptr, (OperandEncoding)0}; + } + + table[Opcode::NOP] = (InstructionEntry){executor_cases::Nop, ZO}; + table[Opcode::HLT] = (InstructionEntry){executor_cases::Hlt, ZO}; + table[Opcode::MOV_R32_IMM32] = (InstructionEntry){executor_cases::Mov_rX_immX<uint32_t, 0xB8>, (OperandEncoding)(I32 | OI)}; + table[Opcode::MOV_RM32_R32] = (InstructionEntry){executor_cases::Mov_rm32_r32, MR}; + table[Opcode::ADD_RM32_R32] = (InstructionEntry){executor_cases::Add_rm32_r32, MR}; + table[Opcode::ADD_R32_RM32] = (InstructionEntry){executor_cases::Add_r32_rm32, RM}; + + for(uint8_t i = 0; i < 255;) + { + if((table[i].m_Encoding & OI) != 0) + { + uint8_t end_offset = std::min(i + 8, 255); + std::fill(table.begin() + i, table.begin() + end_offset, table[i]); + i += 8; + } else { + i++; + } + } + return table; +} + +static constexpr std::array<InstructionEntry, 256> s_InstructionTable = GenerateInstructionTable(); + +const std::array<InstructionEntry, 256>& GetInstructionTable() { + return s_InstructionTable; +} + +namespace executor_cases::helpers { + + // template<uint8_t... I> + // constexpr std::array<ExecutorCase, 256> GenerateEffectiveAddressCases(std::integer_sequence<uint8_t, I...>) { + // return std::array<ExecutorCase, 256> { + // []<uint8_t S>() { + // return [](CPUContext& c){}; + // }.template operator()<I>()... + // }; + // } + + // constexpr static std::array<ExecutorCase, 256> s_EffectiveAddressCases = GenerateEffectiveAddressCases(std::make_integer_sequence<uint8_t, 255>()); + + // uint32_t ResolveEffectiveAddress(CPUContext &cc) { + // // uint32_t req = cc.m_Instruction.m_ModRM.m_Rm << 24 | cc.m_Instruction.m_ModRM.m_Reg << 16 | cc.m_Instruction.m_ModRM.m_State << 8; + + // return 0; + // } + + uint32_t ResolveModRMAddress(CPUContext& cc) { + x86::ModRM modrm = cc.m_Instruction.m_ModRM; + x86::SIB sib = cc.m_Instruction.m_SIB; + + uint32_t value = 0; + + switch(modrm.m_State) { + case x86::ModRMState::LR_DISP32: + case x86::ModRMState::LR_DISP8: + value = cc.m_Registers[modrm.m_Rm] + cc.m_Instruction.m_Displacement[0]; + break; + case x86::ModRMState::DISP32: + std::memcpy(&value, &cc.m_Instruction.m_Displacement, 4); + break; + case x86::ModRMState::LR: + value = cc.m_Registers[modrm.m_Rm]; + break; + case x86::ModRMState::R: + std::cout << "x86::ModRM reached x86::ModRMState::R during ResolveModRMAddress()" << std::endl; + std::cout << "This behavior is unexpected." << std::endl; + break; + default: + throw std::runtime_error("Undefined MODRM state encountered!"); + } + + return value; + } +} diff --git a/src/ExecutorCases.h b/src/ExecutorCases.h new file mode 100644 index 0000000..bbcd8ae --- /dev/null +++ b/src/ExecutorCases.h @@ -0,0 +1,33 @@ +#pragma once + +#include <cstdint> +#include <memory> +#include <array> + +struct CPUContext; + +typedef void (*ExecutorCase)(CPUContext&); + +enum OperandEncoding : uint8_t { + ZO = 0b00000000, + RM = 0b00000001, + MR = 0b00000010, + MI = 0b00000100, + OI = 0b00001000, + I32 = 0b00100000, + I16 = 0b01000000, + I8 = 0b10000000, +}; + +struct InstructionEntry { + ExecutorCase m_Executor; + OperandEncoding m_Encoding; +}; + +const std::array<InstructionEntry, 256>& GetInstructionTable(); + +const std::array<ExecutorCase, 256>& GetExecutorTable(); + +namespace executor_cases::helpers { + uint32_t ResolveModRMAddress(CPUContext& cc); +} diff --git a/src/GUI.cpp b/src/GUI.cpp new file mode 100644 index 0000000..4601e99 --- /dev/null +++ b/src/GUI.cpp @@ -0,0 +1,194 @@ +#include "GUI.h" + +#include <imgui.h> +#include <imgui_internal.h> +#include <backends/imgui_impl_glfw.h> +#include <backends/imgui_impl_opengl3.h> +#include <imgui_memory_editor.h> +#include <glad/glad.h> +#include <GLFW/glfw3.h> +#include <cstdlib> +#include <iostream> +#include "Userspace.h" + +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + { + glfwSetWindowShouldClose(window, true); + return; + } +} + +GUI::GUI(Userspace& user) :m_Userspace(user) { + if(!glfwInit()) + exit(1); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + CreateWindow(); +} + +void GUI::CreateWindow() { + m_Window = glfwCreateWindow(1200, 800, "CPUSecX86", NULL, NULL); + if(!m_Window) + { + glfwTerminate(); + return; + } + + glfwMakeContextCurrent(m_Window); + glfwSetKeyCallback(m_Window, key_callback); + ImGui::CreateContext(); + + int version = gladLoadGL(); + std::cout << "OpenGL Version: " << std::dec << version << std::endl; + + ImGui_ImplGlfw_InitForOpenGL(m_Window, true); + ImGui_ImplOpenGL3_Init("#version 460"); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.FontGlobalScale = 1.5f; +} + +void GUI::Run() { + while (!glfwWindowShouldClose(m_Window)) { + glfwPollEvents(); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + DockingSetup(); + + ControlGUI(); + DebugInfoGUI(); + + static MemoryEditor process_mem; + size_t data_size = 0x10000; + process_mem.DrawWindow("Process Memory", m_Userspace.RetrieveMemory(), data_size); + + if(m_Userspace.GetSystemStatus() == SystemStatus::EXCEPTION) + ErrorGUI(); + + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(m_Window); + } + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); +} + +void GUI::Terminate() { + glfwDestroyWindow(m_Window); + glfwTerminate(); +} + +void GUI::DockingSetup() { + ImGuiID dockspace_id = ImGui::GetID("Dockspace"); + ImGuiViewport *viewport = ImGui::GetMainViewport(); + + if (ImGui::DockBuilderGetNode(dockspace_id) == nullptr) { + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); + ImGuiID dock_id_left = 0; + ImGuiID dock_id_main = dockspace_id; + ImGui::DockBuilderSplitNode(dock_id_main, ImGuiDir_Left, 0.40f, &dock_id_left, &dock_id_main); + ImGuiID dock_id_right = 0; + ImGui::DockBuilderSplitNode(dock_id_main, ImGuiDir_Right, 0.40f, &dock_id_right, &dock_id_main); + // ImGuiID dock_id_left_top = 0; + // ImGuiID dock_id_left_bottom = 0; + // ImGui::DockBuilderSplitNode(dock_id_left, ImGuiDir_Up, 0.50f, &dock_id_left_top, &dock_id_left_bottom); + ImGui::DockBuilderDockWindow("Process Memory", dock_id_main); + ImGui::DockBuilderDockWindow("Debug Information", dock_id_left); + ImGui::DockBuilderDockWindow("Emulator Control", dock_id_right); + ImGui::DockBuilderFinish(dockspace_id); + } + + ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_NoTabBar; + dockspace_flags |= ImGuiDockNodeFlags_PassthruCentralNode; + + ImGui::DockSpaceOverViewport(dockspace_id, viewport, dockspace_flags); +} + +void GUI::ControlGUI() { + ImGui::Begin("Emulator Control"); + static std::string state = "STOPPED"; + if (m_Userspace.GetSystemStatus() == SystemStatus::STOPPED) + state = "STOPPED"; + else if (m_Userspace.GetSystemStatus() == SystemStatus::RUNNING) + state = "RUNNING"; + else if (m_Userspace.GetSystemStatus() == SystemStatus::EXCEPTION) + state = "EXCEPTION"; + + ImGui::Text("Current State: %s", state.c_str()); + if(ImGui::Button("Start")) + m_Userspace.Start(); + ImGui::SameLine(); + if(ImGui::Button("Stop")) + m_Userspace.Stop(); + ImGui::SameLine(); + if (ImGui::Button("Reset")) + m_Userspace.Reset(); + if(ImGui::Button("Step")) + m_Userspace.Step(); + ImGui::End(); +} + +void GUI::ErrorGUI() { + ImGui::PushStyleColor(ImGuiCol_TitleBg, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ResizeGrip, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::Begin("Exception"); + ImGui::Text("An exception was detected! Trace:"); + auto& e = m_Userspace.GetException(); + for (auto i : e) + ImGui::Text("Err: %s", i.c_str()); + ImGui::End(); + ImGui::PopStyleColor(4); +} + +void GUI::DebugInfoGUI() { + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal; + if (m_Userspace.GetSystemStatus() == SystemStatus::RUNNING) flags |= ImGuiInputTextFlags_ReadOnly; + CPUStatus status = m_Userspace.GetCPUStatus(); + + ImGui::Begin("Debug Information"); + ImGui::Text("GP Registers"); + ImGui::InputScalar("EAX", ImGuiDataType_U32, &status.m_Registers[0], NULL, NULL, "%08X", flags); + ImGui::InputScalar("ECX", ImGuiDataType_U32, &status.m_Registers[1], NULL, NULL, "%08X", flags); + ImGui::InputScalar("EDX", ImGuiDataType_U32, &status.m_Registers[2], NULL, NULL, "%08X", flags); + ImGui::InputScalar("EBX", ImGuiDataType_U32, &status.m_Registers[3], NULL, NULL, "%08X", flags); + ImGui::InputScalar("ESP", ImGuiDataType_U32, &status.m_Registers[4], NULL, NULL, "%08X", flags); + ImGui::InputScalar("EBP", ImGuiDataType_U32, &status.m_Registers[5], NULL, NULL, "%08X", flags); + ImGui::InputScalar("ESI", ImGuiDataType_U32, &status.m_Registers[6], NULL, NULL, "%08X", flags); + ImGui::InputScalar("EDI", ImGuiDataType_U32, &status.m_Registers[7], NULL, NULL, "%08X", flags); + ImGui::Separator(); + ImGui::Text("Other Registers"); + ImGui::InputScalar("IP", ImGuiDataType_U32, &status.m_IP, NULL, NULL, "%08X", flags); + ImGui::Separator(); + ImGui::Text("Instruction Information"); + ImGui::Text("Instruction Length: %d", status.m_Instruction.m_Length); + ImGui::Text("Opcode: %s", Opcode2Str(status.m_Instruction.m_Opcode).c_str()); + ImGui::Text("Operand1: %x", status.m_Instruction.m_Operand1); + ImGui::Text("Operand2: %x", status.m_Instruction.m_Operand2); + ImGui::Text("ModRM R/M: %d", status.m_Instruction.m_ModRM.m_Rm); + ImGui::Text("ModRM R: %d", status.m_Instruction.m_ModRM.m_Reg); + ImGui::Text("ModRM Status: %d", status.m_Instruction.m_ModRM.m_State); + ImGui::Text("SIB Scale: %x", status.m_Instruction.m_SIB.m_Scale); + ImGui::Text("SIB Index: %x", status.m_Instruction.m_SIB.m_Index); + ImGui::Text("SIB Base: %x", status.m_Instruction.m_SIB.m_Base); + ImGui::End(); +} + +GUI::~GUI() { + Terminate(); +} diff --git a/src/GUI.h b/src/GUI.h new file mode 100644 index 0000000..15b6046 --- /dev/null +++ b/src/GUI.h @@ -0,0 +1,27 @@ +#pragma once + +struct GLFWwindow; +struct Userspace; + +class GUI { +public: + GUI(Userspace& user); + ~GUI(); + + void Run(); + +private: + void CreateWindow(); + void Terminate(); + + void DockingSetup(); + + void ControlGUI(); + void DebugInfoGUI(); + + void ErrorGUI(); + + private: + GLFWwindow* m_Window; + Userspace& m_Userspace; +}; diff --git a/src/Instruction.cpp b/src/Instruction.cpp new file mode 100644 index 0000000..c74139b --- /dev/null +++ b/src/Instruction.cpp @@ -0,0 +1,49 @@ +#include "Instruction.h" + +#include "Exceptions.h" + +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!"); + } + + SIB ProcessSIB(uint8_t sib) { + uint8_t scale_mask = 0b11000000; + uint8_t index_mask = 0b00111000; + uint8_t base_mask = 0b00000111; + + uint8_t scale = (sib & scale_mask) >> 6; + uint8_t index = (sib & index_mask) >> 3; + uint8_t base = sib & base_mask; + + if(index == 4) index = x86::Register::SIB_NONE; + if(base == 4) base = x86::Register::SIB_DISP; + + return {.m_Scale = scale, .m_Index = (x86::Register)index, .m_Base = (x86::Register)base}; + } +} + +std::string Opcode2Str(Opcode op) { + switch(op) { + case Opcode::INVALID: return "INVALID"; + case Opcode::ADD_RM32_R32: return "ADD_RM32_R32"; + case Opcode::ADD_R32_RM32: return "ADD_R32_RM32"; + case Opcode::MOV_R32_IMM32 ... 0xBF: return "MOV_R32_IMM32"; + case Opcode::MOV_RM32_R32: return "MOV_RM32_R32"; + case Opcode::NOP: return "NOP"; + case Opcode::HLT: return "HLT"; + } + + std::string what = "Opcode2Str could not find '" + std::to_string(op) + "'!"; + throw CPUException(what); +} diff --git a/src/Instruction.h b/src/Instruction.h new file mode 100644 index 0000000..c80d2ca --- /dev/null +++ b/src/Instruction.h @@ -0,0 +1,110 @@ +#pragma once + +#include <string> +#include <cstdint> + +#include "ExecutorCases.h" +#include "Exceptions.h" + +namespace x86 { + enum Register : uint8_t { + EAX = 0, ECX = 1, EDX = 2, EBX = 3, + ESP = 4, EBP = 5, ESI = 6, EDI = 7, + + SIB_DISP = 254, + SIB_NONE = 255, + }; + + enum class ModRMState : uint8_t + { + INVALID = 0, + LR = 1, + LR_DISP8 = 2, + LR_DISP32 = 3, + DISP32 = 4, + R = 5 + }; + + struct ModRM { + ModRMState m_State; + x86::Register m_Reg; + uint8_t m_Rm; + bool m_SIB; + }; + + struct SIB { + uint8_t m_Scale; + x86::Register m_Index; + x86::Register m_Base; + }; + + constexpr ModRM ProcessMODRM(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; + bool SIB = false; + + switch(mod) { + case 0b00000000: + state = ModRMState::LR; + if(rm == 0b00000101) + state = ModRMState::DISP32; + else if(rm == 0b00000100) + SIB = true; + break; + case 0b01000000: + state = ModRMState::LR_DISP8; + if(rm == 0b00000100) + SIB = true; + break; + case 0b10000000: + state = ModRMState::LR_DISP32; + if(rm == 0b00000100) + SIB = true; + break; + case 0b11000000: + state = ModRMState::R; + break; + default: + throw CPUException("Mod R/M Unknown exception!"); + } + + return {.m_State = state, .m_Reg = (x86::Register)reg, .m_Rm = rm, .m_SIB = SIB}; + } + + SIB ProcessSIB(uint8_t sib); + + // Helpers + std::string Register2Str(x86::Register reg); +} + +enum Opcode : uint8_t { + INVALID = 0, + NOP = 0x90, + HLT = 0xF4, + MOV_R32_IMM32 = 0xB8, + MOV_RM32_R32 = 0x89, + ADD_RM32_R32 = 0x01, + ADD_R32_RM32 = 0x03, +}; + +std::string Opcode2Str(Opcode op); + +struct Instruction{ + ExecutorCase m_Func; + uint8_t m_Prefix; + Opcode m_Opcode; + size_t m_Length; + uint32_t m_Operand1; + uint32_t m_Operand2; + x86::ModRM m_ModRM; + x86::SIB m_SIB; + uint8_t m_Displacement[4]; + uint8_t m_DisplacementSize; +}; diff --git a/src/InstructionModifierLookup.cpp b/src/InstructionModifierLookup.cpp new file mode 100644 index 0000000..87c0350 --- /dev/null +++ b/src/InstructionModifierLookup.cpp @@ -0,0 +1,76 @@ +#include "InstructionModifierLookup.h" + +#include "CPUContext.h" +#include "Instruction.h" +#include "Bus.h" + +#include <array> +#include <cstdint> +#include <utility> +#include <cstring> +#include <iostream> + +void Mod32_SIB(CPUContext &cc) { + std::cout << "[Mod32_SIB] SIB byte encountered." << std::endl; +} + +void Mod32_LR(CPUContext& cc) { + std::cout << "[Mod32_LR] Executed." << std::endl; + if (cc.m_Instruction.m_ModRM.m_SIB) { + uint32_t src = cc.m_Bus->AccessX<uint32_t>(cc.m_InstructionPointer + cc.m_Instruction.m_Length); + std::memcpy(&cc.m_Instruction.m_Displacement, &src, 4); + Mod32_SIB(cc); + } +} + +void Mod32_DISP32(CPUContext& cc) { + uint32_t src = cc.m_Bus->AccessX<uint32_t>(cc.m_InstructionPointer + cc.m_Instruction.m_Length); + std::memcpy(&cc.m_Instruction.m_Displacement, &src, 4); + cc.m_Instruction.m_Length += 4; + if (cc.m_Instruction.m_ModRM.m_SIB) + Mod32_SIB(cc); +} + +template<uint8_t RM> +void Mod32_LRDISP32(CPUContext& cc) { + uint32_t disp = cc.m_Registers[RM] + cc.m_Bus->AccessX<uint32_t>(cc.m_Instruction.m_Length); + std::memcpy(&cc.m_Instruction.m_Displacement, &disp, 4); + cc.m_Instruction.m_Length += 4; + if (cc.m_Instruction.m_ModRM.m_SIB) + Mod32_SIB(cc); + } + +template<uint8_t RM> +void Mod32_LRDISP8(CPUContext& cc) { + uint32_t disp = cc.m_Registers[RM] + cc.m_Bus->AccessX<uint8_t>(cc.m_Instruction.m_Length); + cc.m_Instruction.m_Displacement[0] = disp; + cc.m_Instruction.m_Length += 1; + if (cc.m_Instruction.m_ModRM.m_SIB) + Mod32_SIB(cc); +} + +void Mod32_R(CPUContext& cc) { + std::cout << "[Mod32_R] Executed." << std::endl; +} + +template<uint8_t...Is> +static constexpr std::array<ModifierEntry, 256> GenerateModRM32(std::integer_sequence<uint8_t, Is...>) { + return std::array<ModifierEntry, 256>{ + ([]<uint8_t I>() { + constexpr auto modrm = x86::ProcessMODRM(I); + + switch (modrm.m_State) { + case x86::ModRMState::LR: return Mod32_LR; + case x86::ModRMState::LR_DISP32: return Mod32_LRDISP32<modrm.m_Rm>; + case x86::ModRMState::LR_DISP8: return Mod32_LRDISP8<modrm.m_Rm>; + case x86::ModRMState::DISP32: return Mod32_DISP32; + case x86::ModRMState::R: return Mod32_R; + } + + }.template operator()<Is>())... // provide the lambda with a proper template argument + }; +} + +static constexpr std::array<ModifierEntry, 256> s_ModRM32 = GenerateModRM32(std::make_integer_sequence<uint8_t, 255>{}); + +std::array<ModifierEntry, 256> GetMod32Table() { return s_ModRM32; } diff --git a/src/InstructionModifierLookup.h b/src/InstructionModifierLookup.h new file mode 100644 index 0000000..1d82023 --- /dev/null +++ b/src/InstructionModifierLookup.h @@ -0,0 +1,11 @@ +#pragma once + +#include <array> + +struct CPUContext; + +typedef void (*ModifierEntry)(CPUContext&); + +std::array<ModifierEntry, 256> GetMod32Table(); + +std::array<ModifierEntry, 256> GetSIBTable(); diff --git a/src/Misc.cpp b/src/Misc.cpp new file mode 100644 index 0000000..767aed6 --- /dev/null +++ b/src/Misc.cpp @@ -0,0 +1,12 @@ +#include "Misc.h" + +#include "ExecutorCases.h" +#include "CPUContext.h" + +#include <iostream> + +namespace executor_cases { + void Nop(CPUContext& cc){ + std::cout << "[Instruction] nop" << std::endl; + } +} diff --git a/src/Misc.h b/src/Misc.h new file mode 100644 index 0000000..8f52706 --- /dev/null +++ b/src/Misc.h @@ -0,0 +1,7 @@ +#pragma once + +struct CPUContext; + +namespace executor_cases { + void Nop(CPUContext& cc); +} diff --git a/src/RAM.cpp b/src/RAM.cpp new file mode 100644 index 0000000..fc16777 --- /dev/null +++ b/src/RAM.cpp @@ -0,0 +1,12 @@ +#include "RAM.h" + +RAM::RAM() { + for(int i = 0; i < 992 * 1024; i++) + { + m_Memory[i] = 0x90; + } +} + +uint8_t* RAM::Data() { + return m_Memory; +} diff --git a/src/RAM.h b/src/RAM.h new file mode 100644 index 0000000..c432ea1 --- /dev/null +++ b/src/RAM.h @@ -0,0 +1,14 @@ +#pragma once + +#include <cstdint> + +class RAM { + public: + RAM(); + ~RAM() = default; + + uint8_t* Data(); + + private: + uint8_t m_Memory[992 * 1024]; +}; diff --git a/src/Userspace.cpp b/src/Userspace.cpp new file mode 100644 index 0000000..0dc2425 --- /dev/null +++ b/src/Userspace.cpp @@ -0,0 +1,99 @@ +#include "Userspace.h" + +#include "Bus.h" +#include "RAM.h" + +#include <thread> + +Userspace::Userspace() : m_RAM(std::make_shared<RAM>()), m_ProgramMemory(std::make_shared<RAM>()), m_Bus(std::make_shared<Bus>(m_RAM)), m_CPU(m_Bus) { + std::cout << "[Userspace] Emulation started." << std::endl; + m_ApplicationRunning = true; + m_Status = SystemStatus::STOPPED; +} + +void Userspace::Upload2Memory(uint8_t bytes[], size_t len) { + m_ProgramMemoryLen = len; + std::memcpy(m_RAM->Data(), bytes, len); + std::memcpy(m_ProgramMemory->Data(), bytes, len); +} + +uint8_t* Userspace::RetrieveMemory() { + return m_RAM->Data(); +} + +void Userspace::Run() { + while(m_ApplicationRunning) { + EmulatorLoop(); + } +} + +void Userspace::StepCPU() { + try { + m_CPU.Step(); + } catch (CPUException& c) { + m_Status = SystemStatus::EXCEPTION; + m_ExceptionMessages.push_back(c.GetMessage()); + } +} + +void Userspace::EmulatorLoop() { + while(m_Status == SystemStatus::RUNNING) { + if(!m_CPU.IsHalted()) { + StepCPU(); + } + + if (m_CPU.IsHalted()) { + if(m_Status == SystemStatus::RUNNING) + m_Status = SystemStatus::STOPPED; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + if(m_Status == SystemStatus::STOPPED) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); +} + +void Userspace::Reset() { + if(m_CPU.IsHalted()) m_Status = SystemStatus::STOPPED; + m_CPU.Reset(); + std::memcpy(m_RAM->Data(), m_ProgramMemory->Data(), m_ProgramMemoryLen); + m_ExceptionMessages.clear(); +} + +void Userspace::Start() { + m_Status = SystemStatus::RUNNING; +} + +void Userspace::Step() { + if(!m_CPU.IsHalted()) + { + StepCPU(); + std::cout << "[Userspace] CPU stepped!" << std::endl; + } + else + { + std::cout << "[Userspace] CPU is halted! Cannot step." << std::endl; + } +} + +void Userspace::Stop() { + m_Status = SystemStatus::STOPPED; + std::cout << "[Userspace] Emulation stopped." << std::endl; +} + +void Userspace::Exit() { + m_Status = SystemStatus::STOPPED; + m_ApplicationRunning = false; + std::cout << "[Userspace] Emulation exited." << std::endl; +} + +SystemStatus Userspace::GetSystemStatus() { + return m_Status; +} + +CPUStatus Userspace::GetCPUStatus() { + return m_CPU.GetStatus(); +} + +std::vector<std::string>& Userspace::GetException() { + return m_ExceptionMessages; +} diff --git a/src/Userspace.h b/src/Userspace.h new file mode 100644 index 0000000..3f9db8c --- /dev/null +++ b/src/Userspace.h @@ -0,0 +1,52 @@ +#pragma once + +class Bus; + +#include <cstdint> +#include <memory> +#include <atomic> +#include <vector> + +#include "CPU.h" + +enum class SystemStatus { + STOPPED, + RUNNING, + EXCEPTION +}; + +class Userspace { + public: + Userspace(); + ~Userspace() = default; + + void Upload2Memory(uint8_t bytes[], size_t len); + uint8_t* RetrieveMemory(); + + void Run(); + void Start(); + void Stop(); + void Step(); + void Reset(); + void Exit(); + + SystemStatus GetSystemStatus(); + CPUStatus GetCPUStatus(); + std::vector<std::string>& GetException(); + + private: + std::shared_ptr<RAM> m_RAM; + std::shared_ptr<Bus> m_Bus; + CPU m_CPU; + std::atomic<SystemStatus> m_Status = SystemStatus::STOPPED; + std::atomic<bool> m_ApplicationRunning = false; + + std::vector<std::string> m_ExceptionMessages; + + std::shared_ptr<RAM> m_ProgramMemory; + size_t m_ProgramMemoryLen; + + private: + void StepCPU(); + void EmulatorLoop(); +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5dd1edb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,35 @@ +#include <iostream> + +#include "Userspace.h" +#include "GUI.h" +#include <thread> + +// nop +// mov eax, 0xF4 +// mov ecx, 0x8000 +// mov edx, 0x8000 +// add DWORD PTR [0x8011], eax +uint8_t test[] = { + 0x90, + 0xB8, 0xF4, 0x00, 0x00, 0x00, + 0xB9, 0x00, 0x80, 0x00, 0x00, + 0xBA, 0x00, 0x80, 0x00, 0x00, + 0x01, 0x05, 0x16, 0x80, 0x00, 0x00, + 0x00, +}; + +int main(int argc, char** argv) { + Userspace user; + GUI gui(user); + + user.Upload2Memory(test, 23); + std::thread tuserspace(&Userspace::Run, &user); + + gui.Run(); + + user.Exit(); + + tuserspace.join(); + + return 0; +} |
