Feature: Add mov_rm32_r32 instruction & more expressive execution with CPU trace

This commit is contained in:
0x221E
2026-02-05 14:57:23 +01:00
parent ac6bf8cb46
commit caadd2f706
12 changed files with 120 additions and 25 deletions

View File

@@ -5,29 +5,26 @@
#include "Bus.h" #include "Bus.h"
#include <stdexcept> #include <stdexcept>
#include <iostream>
namespace executor_cases { namespace executor_cases {
void Add_rm32_r32(CPUContext& cc){ void Add_rm32_r32(CPUContext& cc){
x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM; x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM;
std::cout << "[Instruction] ";
switch(modrm.m_State) { switch(modrm.m_State) {
case x86::ModRMState::R: case x86::ModRMState::R:
cc.m_Registers[modrm.m_Rm] += cc.m_Registers[modrm.m_Reg]; cc.m_Registers[modrm.m_Rm] += cc.m_Registers[modrm.m_Reg];
break; std::cout << "add " << x86::Register2Str((x86::Register)modrm.m_Rm) << ", " << x86::Register2Str((x86::Register)modrm.m_Rm);
case x86::ModRMState::LR:
helpers::Add_rm_dst(cc, cc.m_Registers[modrm.m_Rm]);
break;
case x86::ModRMState::LR_DISP8:
case x86::ModRMState::LR_DISP32:
helpers::Add_rm_dst(cc, (uint32_t)(cc.m_Registers[modrm.m_Rm] + cc.m_Instruction.m_Operand1));
break;
case x86::ModRMState::DISP32:
helpers::Add_rm_dst(cc, cc.m_Instruction.m_Operand1);
break; break;
default: default:
throw std::runtime_error("Invalid ModRM State encountered during Add_rm32_r32"); 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;
} }
namespace helpers { namespace helpers {

View File

@@ -17,7 +17,7 @@ public:
{ {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned int of any size smaller than 8 bytes!"); 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; // std::cout << "Bus write: " << std::hex << address << std::endl;
switch(address) switch(address)
{ {
@@ -37,7 +37,7 @@ public:
{ {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned int of any size smaller than 8 bytes!"); 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; //std::cout << "Bus access: " << std::hex << address << std::endl;
switch(address) switch(address)
{ {

View File

@@ -3,6 +3,7 @@
#include <stdexcept> #include <stdexcept>
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include <bitset>
#include <stdlib.h> #include <stdlib.h>
#include <array> #include <array>
#include <cassert> #include <cassert>
@@ -10,10 +11,15 @@
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}) { 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; m_InstructionPointer = 0x00008000;
for(int i = 0; i < 16; i++) for(int i = 0; i < 8; i++)
{ {
m_Registers[i] = 0; m_Registers[i] = 0;
} }
for(int i = 0; i < 8; i++)
{
m_SegmentRegisters[i] = 0;
}
} }
void CPU::Step() { void CPU::Step() {
@@ -21,6 +27,23 @@ void CPU::Step() {
Execute(); 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::FetchDecode() { void CPU::FetchDecode() {
uint8_t opcode_raw = m_Bus->AccessX<uint8_t>(m_InstructionPointer); uint8_t opcode_raw = m_Bus->AccessX<uint8_t>(m_InstructionPointer);
Opcode opcode = static_cast<Opcode>(opcode_raw); Opcode opcode = static_cast<Opcode>(opcode_raw);
@@ -43,18 +66,24 @@ void CPU::FetchDecode() {
m_Instruction.m_Length = 2; m_Instruction.m_Length = 2;
FetchModRMFields(); FetchModRMFields();
break; break;
case Opcode::MOV_RM32_R32:
m_Instruction.m_Opcode = opcode;
m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1));
m_Instruction.m_Length = 2;
FetchModRMFields();
break;
} }
m_InstructionPointer += m_Instruction.m_Length; m_InstructionPointer += m_Instruction.m_Length;
} }
void CPU::Execute() { void CPU::Execute() {
std::cout << "Executing... \n";
uint8_t opcode_value = static_cast<uint8_t>(m_Instruction.m_Opcode); uint8_t opcode_value = static_cast<uint8_t>(m_Instruction.m_Opcode);
auto& exec_table = GetExecutorTable(); auto& exec_table = GetExecutorTable();
if(exec_table[opcode_value]) if(exec_table[opcode_value])
{ {
exec_table[opcode_value](m_Context); exec_table[opcode_value](m_Context);
Dump();
return; return;
} }
throw std::runtime_error("Opcode not found!"); throw std::runtime_error("Opcode not found!");

View File

@@ -15,6 +15,8 @@ public:
public: public:
void Step(); void Step();
void Dump();
bool IsHalted() { return m_IsHalted; } bool IsHalted() { return m_IsHalted; }
private: private:
@@ -22,14 +24,14 @@ private:
void Execute(); void Execute();
private: private:
uint32_t m_Registers[16]; uint32_t m_Registers[8];
uint16_t m_SegmentRegisters[8];
uint32_t m_InstructionPointer; uint32_t m_InstructionPointer;
uint32_t m_Flags; uint32_t m_Flags;
bool m_IsHalted; bool m_IsHalted;
std::shared_ptr<Bus> m_Bus; std::shared_ptr<Bus> m_Bus;
uint64_t m_InstructionRaw;
Instruction m_Instruction; Instruction m_Instruction;
CPUContext m_Context; CPUContext m_Context;

View File

@@ -6,7 +6,7 @@
namespace executor_cases { namespace executor_cases {
void Hlt(CPUContext& cc){ void Hlt(CPUContext& cc){
std::cout << "Program halted!" << std::endl; std::cout << "[Instruction] hlt" << std::endl;
cc.m_IsHalted = true; cc.m_IsHalted = true;
} }
} }

View File

@@ -2,9 +2,32 @@
#include "ExecutorCases.h" #include "ExecutorCases.h"
#include "Instruction.h" #include "Instruction.h"
#include "Bus.h"
#include <iostream>
namespace executor_cases { namespace executor_cases {
void Mov_r32_imm32(CPUContext& cc) { void Mov_r32_imm32(CPUContext& cc) {
std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)cc.m_Instruction.m_Operand1) << ", " << std::hex << cc.m_Instruction.m_Operand2 << std::endl;
cc.m_Registers[cc.m_Instruction.m_Operand1] = cc.m_Instruction.m_Operand2; cc.m_Registers[cc.m_Instruction.m_Operand1] = cc.m_Instruction.m_Operand2;
} }
void Mov_rm32_r32(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_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;
}
} }

View File

@@ -4,4 +4,5 @@ struct CPUContext;
namespace executor_cases { namespace executor_cases {
void Mov_r32_imm32(CPUContext& cc); void Mov_r32_imm32(CPUContext& cc);
void Mov_rm32_r32(CPUContext& cc);
} }

View File

@@ -20,6 +20,7 @@ constexpr std::array<ExecutorCase, 255> GenerateExecutorTable(){
table[Opcode::NOP] = executor_cases::Nop; table[Opcode::NOP] = executor_cases::Nop;
table[Opcode::HLT] = executor_cases::Hlt; table[Opcode::HLT] = executor_cases::Hlt;
table[Opcode::MOV_R32_IMM32] = executor_cases::Mov_r32_imm32; table[Opcode::MOV_R32_IMM32] = executor_cases::Mov_r32_imm32;
table[Opcode::MOV_RM32_R32] = executor_cases::Mov_rm32_r32;
table[Opcode::ADD_RM32_R32] = executor_cases::Add_rm32_r32; table[Opcode::ADD_RM32_R32] = executor_cases::Add_rm32_r32;
return table; return table;
} }
@@ -29,3 +30,33 @@ static constexpr std::array<ExecutorCase, 255> s_ExecutorTable = GenerateExecuto
const std::array<ExecutorCase, 255>& GetExecutorTable() { const std::array<ExecutorCase, 255>& GetExecutorTable() {
return s_ExecutorTable; return s_ExecutorTable;
} }
namespace executor_cases::helpers {
uint32_t ResolveModRMAddress(CPUContext& cc) {
x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM;
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_Operand1;
break;
case x86::ModRMState::DISP32:
value = cc.m_Instruction.m_Operand1;
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;
}
}

View File

@@ -22,3 +22,7 @@ struct CPUContext {
typedef void (*ExecutorCase)(CPUContext&); typedef void (*ExecutorCase)(CPUContext&);
const std::array<ExecutorCase, 255>& GetExecutorTable(); const std::array<ExecutorCase, 255>& GetExecutorTable();
namespace executor_cases::helpers {
uint32_t ResolveModRMAddress(CPUContext& cc);
}

View File

@@ -37,6 +37,7 @@ enum Opcode : uint8_t {
NOP = 0x90, NOP = 0x90,
HLT = 0xF4, HLT = 0xF4,
MOV_R32_IMM32 = 0xB8, MOV_R32_IMM32 = 0xB8,
MOV_RM32_R32 = 0x89,
ADD_RM32_R32 = 0x01, ADD_RM32_R32 = 0x01,
}; };
@@ -47,6 +48,7 @@ struct Instruction{
size_t m_Length; size_t m_Length;
uint32_t m_Operand1; uint32_t m_Operand1;
uint32_t m_Operand2; uint32_t m_Operand2;
// uint8_t m_Displacement[];
union { union {
x86::ModRM m_ModRM; x86::ModRM m_ModRM;
} optional; } optional;

View File

@@ -6,6 +6,6 @@
namespace executor_cases { namespace executor_cases {
void Nop(CPUContext& cc){ void Nop(CPUContext& cc){
std::cout << "No op" << std::endl; std::cout << "[Instruction] nop" << std::endl;
} }
} }

View File

@@ -2,19 +2,25 @@
#include "Metal.h" #include "Metal.h"
// nop
// mov eax, 0xF4 // mov eax, 0xF4
// mov ecx, 0x8000 // mov ecx, 0x8000
// add [0x8010], eax // mov edx, 0x8000
// mov [edx], eax
// add DWORD PTR [0x8011], eax
uint8_t test[] = { uint8_t test[] = {
0x90,
0xB8, 0xF4, 0x00, 0x00, 0x00, 0xB8, 0xF4, 0x00, 0x00, 0x00,
0xB9, 0x00, 0x80, 0x00, 0x00, 0xB9, 0x00, 0x80, 0x00, 0x00,
0x01, 0x05, 0x10, 0x80, 0x00, 0x00, 0xBA, 0x00, 0x80, 0x00, 0x00,
0x89, 0x02,
0x01, 0x05, 0x18, 0x80, 0x00, 0x00,
0x00, 0x00,
}; };
int main(int argc, char** argv) { int main(int argc, char** argv) {
Metal metal; Metal metal;
metal.Upload2Memory(test, 17); metal.Upload2Memory(test, 25);
metal.Run(); metal.Run();
return 0; return 0;
} }