diff --git a/src/debug.cpp b/src/debug.cpp index 3f04e3c..2d8c73d 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -1,5 +1,7 @@ #include "debug.hpp" +#include + GDBStub::GDBStub(VM &vm, int port) : server_fd(-1), client_fd(-1), running(false), vm(vm) { server_fd = socket(AF_INET, SOCK_STREAM, 0); @@ -28,6 +30,12 @@ GDBStub::GDBStub(VM &vm, int port) throw std::runtime_error("Failed to accept connection."); } + // GDB protocol operates with small packets, and thus we need to disable + // the sending delay (Nagle's Algorithm). Otherwise gdb commands would have + // noticeable lag. + int flag = 1; + setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); + std::cout << "GDB connected!" << std::endl; running = true; } @@ -135,10 +143,13 @@ void GDBStub::handle_packet(const std::string &packet) { // Getting specific register int regnum = std::stoi(packet.substr(1), nullptr, 16); - uint32_t reg_value = 0; + uint32_t reg_value = vm.read_register(regnum); std::ostringstream response; - response << std::hex << std::setfill('0') << std::setw(8) << reg_value; + for (int i = 0; i < 4; ++i) { + response << std::hex << std::setfill('0') << std::setw(2) + << ((reg_value >> (i * 8)) & 0xFF); + } send_packet(response.str()); break; @@ -167,6 +178,7 @@ void GDBStub::handle_packet(const std::string &packet) { break; case 's': + vm.step(); send_packet("S05"); // TODO: step execution break; @@ -203,7 +215,11 @@ bool GDBStub::parse_memory_request(const std::string &packet, size_t &addr, std::string GDBStub::read_registers() { std::ostringstream out; for (int i = 0; i < 32; ++i) { - out << std::hex << std::setfill('0') << std::setw(8) << vm.read_register(i); + uint32_t reg_value = vm.read_register(i); + for (int i = 0; i < 4; ++i) { + out << std::hex << std::setfill('0') << std::setw(2) + << ((reg_value >> (i * 8)) & 0xFF); + } } return out.str(); } diff --git a/src/vm.cpp b/src/vm.cpp index 3f62218..62e08eb 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -2,22 +2,21 @@ #include #include +#include #include #include #include -#include - inline int32_t sign_extend(int32_t value, int bits) { int32_t mask = 1 << (bits - 1); return (value ^ mask) - mask; } -std::vector load_program(const std::string& filename, size_t memory_size) -{ +std::vector load_program(const std::string& filename, + size_t memory_size) { std::vector memory(memory_size, 0); - std::ifstream file(filename, std::ios::binary|std::ios::ate); + std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) { throw std::runtime_error("Failed to open file: " + filename); @@ -34,7 +33,8 @@ std::vector load_program(const std::string& filename, size_t memory_s file.read(reinterpret_cast(&memory[0]), file_size); if (!file) { - throw std::runtime_error("Failed to read the complete program into memory."); + throw std::runtime_error( + "Failed to read the complete program into memory."); } file.close(); @@ -42,17 +42,19 @@ std::vector load_program(const std::string& filename, size_t memory_s return memory; } -VM::VM(std::vector memory) - : memory_(memory) { - -} +VM::VM(std::vector memory) : memory_(memory) {} std::vector VM::read_memory(size_t start, size_t size) { + if (start + size > memory_.size()) { + return std::vector(size, 0); + } return std::vector(memory_.begin() + start, memory_.begin() + start + size); } uint32_t VM::read_register(size_t regnum) { + if (regnum == 32) return pc; + if (regnum >= NUM_REGISTERS) { throw std::runtime_error("Register out of range"); } @@ -60,55 +62,55 @@ uint32_t VM::read_register(size_t regnum) { return registers[regnum]; } -void VM::eval() { +void VM::step() { size_t memory_size = memory_.size(); - uint8_t *memory = &memory_[0]; + uint8_t* memory = &memory_[0]; - bool running = true; - while (pc < memory_size && running) { - uint32_t instr = *(uint32_t*)&memory[pc]; - if (instr == 0) break; - //std::cout << "pc: " << std::hex << pc << std::dec << "\n"; - // std::cout << "instr: " << std::hex << instr << "\n"; - pc += 4; + uint32_t instr = *(uint32_t*)&memory[pc]; + // std::cout << "pc: " << std::hex << pc << std::dec << "\n"; + // std::cout << "instr: " << std::hex << instr << "\n"; + pc += 4; - // Decode instruction - uint32_t opcode = instr & 0x7F; - uint32_t rd = (instr >> 7) & 0x1F; - uint32_t funct3 = (instr >> 12) & 0x7; - uint32_t rs1 = (instr >> 15) & 0x1F; - uint32_t rs2 = (instr >> 20) & 0x1F; - uint32_t funct7 = (instr >> 25); - int32_t imm; + // Decode instruction + uint32_t opcode = instr & 0x7F; + uint32_t rd = (instr >> 7) & 0x1F; + uint32_t funct3 = (instr >> 12) & 0x7; + uint32_t rs1 = (instr >> 15) & 0x1F; + uint32_t rs2 = (instr >> 20) & 0x1F; + uint32_t funct7 = (instr >> 25); + int32_t imm; - switch (opcode) { - case 0x33: { // R-type + switch (opcode) { + case 0x33: { // R-type if (funct7 == 0x00) { - if (funct3 == 0x0) { // ADD + if (funct3 == 0x0) { // ADD registers[rd] = registers[rs1] + registers[rs2]; - } else if (funct3 == 0x04) { // XOR + } else if (funct3 == 0x04) { // XOR registers[rd] = registers[rs1] ^ registers[rs2]; - } else if (funct3 == 0x06) { // OR + } else if (funct3 == 0x06) { // OR registers[rd] = registers[rs1] | registers[rs2]; - } else if (funct3 == 0x07) { // AND + } else if (funct3 == 0x07) { // AND registers[rd] = registers[rs1] & registers[rs2]; - } else if (funct3 == 0x01) { // SLL + } else if (funct3 == 0x01) { // SLL registers[rd] = registers[rs1] << registers[rs2]; - } else if (funct3 == 0x05) { // SRL + } else if (funct3 == 0x05) { // SRL uint32_t value = registers[rs1]; uint32_t shift_amount = registers[rs2] & 0x1F; registers[rd] = value << shift_amount; - } else if (funct3 == 0x02) { // SLT - registers[rd] = (static_cast(registers[rs1]) < static_cast(registers[rs2]))?0:1; - } else if (funct3 == 0x03) { // SLTU + } else if (funct3 == 0x02) { // SLT + registers[rd] = (static_cast(registers[rs1]) < + static_cast(registers[rs2])) + ? 0 + : 1; + } else if (funct3 == 0x03) { // SLTU registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0; } else { throw std::runtime_error("Unknown R-type instruction"); } } else if (funct7 == 0x20) { - if (funct3 == 0x0) { // SUB + if (funct3 == 0x0) { // SUB registers[rd] = registers[rs1] - registers[rs2]; - } else if (funct3 == 0x05) { // SRA + } else if (funct3 == 0x05) { // SRA // Only the lower 5 bits are used for shift int32_t value = static_cast(registers[rs1]); int32_t shift_amount = registers[rs2] & 0x1F; @@ -117,47 +119,51 @@ void VM::eval() { throw std::runtime_error("Unknown R-type instruction"); } } else if (funct7 == 0x01) { - if (funct3 == 0x0) { // MUL - int64_t result = static_cast(static_cast(registers[rs1])) * - static_cast(static_cast(registers[rs2])); + if (funct3 == 0x0) { // MUL + int64_t result = + static_cast(static_cast(registers[rs1])) * + static_cast(static_cast(registers[rs2])); registers[rd] = static_cast(result); - } else if (funct3 == 0x1) { // MULH - int64_t result = static_cast(static_cast(registers[rs1])) * - static_cast(static_cast(registers[rs2])); + } else if (funct3 == 0x1) { // MULH + int64_t result = + static_cast(static_cast(registers[rs1])) * + static_cast(static_cast(registers[rs2])); registers[rd] = static_cast(result >> 32); - } else if (funct3 == 0x2) { // MULSU - int64_t result = static_cast(static_cast(registers[rs1])) * - static_cast(registers[rs2]); + } else if (funct3 == 0x2) { // MULSU + int64_t result = + static_cast(static_cast(registers[rs1])) * + static_cast(registers[rs2]); registers[rd] = static_cast(result >> 32); - } else if (funct3 == 0x3) { // MULU + } else if (funct3 == 0x3) { // MULU uint64_t result = static_cast(registers[rs1]) * - static_cast(registers[rs2]); - registers[rd] = static_cast(result >> 32); // Upper 32 bits - } else if (funct3 == 0x4) { // DIV + static_cast(registers[rs2]); + registers[rd] = static_cast(result >> 32); // Upper 32 bits + } else if (funct3 == 0x4) { // DIV int32_t dividend = static_cast(registers[rs1]); int32_t divisor = static_cast(registers[rs2]); if (divisor == 0) { - registers[rd] = -1; // Division by zero result + registers[rd] = -1; // Division by zero result } else if (dividend == INT32_MIN && divisor == -1) { - registers[rd] = dividend; // Overflow case + registers[rd] = dividend; // Overflow case } else { registers[rd] = dividend / divisor; } - } else if (funct3 == 0x5) { // DIVU + } else if (funct3 == 0x5) { // DIVU uint32_t dividend = registers[rs1]; uint32_t divisor = registers[rs2]; registers[rd] = (divisor == 0) ? UINT32_MAX : dividend / divisor; - } else if (funct3 == 0x6) { // REM + } else if (funct3 == 0x6) { // REM int32_t dividend = static_cast(registers[rs1]); int32_t divisor = static_cast(registers[rs2]); if (divisor == 0) { - registers[rd] = dividend; // Remainder with zero divisor is the dividend + registers[rd] = + dividend; // Remainder with zero divisor is the dividend } else if (dividend == INT32_MIN && divisor == -1) { - registers[rd] = 0; // Overflow case + registers[rd] = 0; // Overflow case } else { registers[rd] = dividend % divisor; } - } else if (funct3 == 0x7) { // REMU + } else if (funct3 == 0x7) { // REMU uint32_t dividend = registers[rs1]; uint32_t divisor = registers[rs2]; registers[rd] = (divisor == 0) ? dividend : dividend % divisor; @@ -169,63 +175,63 @@ void VM::eval() { } break; } - case 0x13: { // I-type (ADDI) - imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate - if (funct3 == 0x0) { // ADDI + case 0x13: { // I-type (ADDI) + imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate + if (funct3 == 0x0) { // ADDI registers[rd] = registers[rs1] + imm; } else { throw std::runtime_error("Unknown I-type instruction"); } break; } - case 0x63: { // B-type (branches) + case 0x63: { // B-type (branches) imm = ((instr >> 7) & 0x1E) | ((instr >> 20) & 0x7E0) | - ((instr >> 19) & 0x800) | ((instr >> 31) << 12); - imm = sign_extend(imm, 13); // Sign-extend 13-bit immediate - if (funct3 == 0x0) { // BEQ + ((instr >> 19) & 0x800) | ((instr >> 31) << 12); + imm = sign_extend(imm, 13); // Sign-extend 13-bit immediate + if (funct3 == 0x0) { // BEQ if (registers[rs1] == registers[rs2]) { - pc += imm - 4; // Offset PC (adjust for pre-increment) + pc += imm - 4; // Offset PC (adjust for pre-increment) } - } else if (funct3 == 0x1) { // BNE + } else if (funct3 == 0x1) { // BNE if (registers[rs1] != registers[rs2]) { - pc += imm - 4; // Offset PC + pc += imm - 4; // Offset PC } - } else if (funct3 == 0x4) { // BLT + } else if (funct3 == 0x4) { // BLT if (static_cast(registers[rs1]) < static_cast(registers[rs2])) { pc += imm - 4; } - } else if (funct3 == 0x5) { // BGE + } else if (funct3 == 0x5) { // BGE if (static_cast(registers[rs1]) >= static_cast(registers[rs2])) { pc += imm - 4; } - } else if (funct3 == 0x6) { // BLTU + } else if (funct3 == 0x6) { // BLTU if (registers[rs1] < registers[rs2]) pc += imm - 4; - } else if (funct3 == 0x7) { // BGEU + } else if (funct3 == 0x7) { // BGEU if (registers[rs1] >= registers[rs2]) pc += imm - 4; } else { throw std::runtime_error("Unknown B-type instruction"); } break; } - case 0x03: { // I-type (loads) - imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate - if (funct3 == 0x00) { // LB + case 0x03: { // I-type (loads) + imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate + if (funct3 == 0x00) { // LB uint32_t addr = registers[rs1] + imm; if (addr + 1 > memory_size) { throw std::runtime_error("Memory access out of bounds"); } registers[rd] = 0; std::memcpy(®isters[rd], memory + addr, sizeof(uint8_t)); - } else if (funct3 == 0x01) { // LH + } else if (funct3 == 0x01) { // LH uint32_t addr = registers[rs1] + imm; if (addr + 2 > memory_size) { throw std::runtime_error("Memory access out of bounds"); } registers[rd] = 0; std::memcpy(®isters[rd], memory + addr, sizeof(uint16_t)); - } else if (funct3 == 0x2) { // LW + } else if (funct3 == 0x2) { // LW uint32_t addr = registers[rs1] + imm; if (addr + 4 > memory_size) { throw std::runtime_error("Memory access out of bounds"); @@ -236,22 +242,22 @@ void VM::eval() { } break; } - case 0x23: { // S-type (SW) + case 0x23: { // S-type (SW) imm = ((instr >> 7) & 0x1F) | (((instr >> 25) & 0x7F) << 5); - imm = sign_extend(imm, 12); // Sign-extend 12-bit immediate - if (funct3 == 0x0) { // SB + imm = sign_extend(imm, 12); // Sign-extend 12-bit immediate + if (funct3 == 0x0) { // SB uint32_t addr = registers[rs1] + imm; if (addr + 1 > memory_size) { throw std::runtime_error("Memory access out of bounds"); } std::memcpy(memory + addr, ®isters[rs2], sizeof(uint8_t)); - } else if (funct3 == 0x1) { // SH + } else if (funct3 == 0x1) { // SH uint32_t addr = registers[rs1] + imm; if (addr + 2 > memory_size) { throw std::runtime_error("Memory access out of bounds"); } std::memcpy(memory + addr, ®isters[rs2], sizeof(uint16_t)); - } else if (funct3 == 0x2) { // SW + } else if (funct3 == 0x2) { // SW uint32_t addr = registers[rs1] + imm; if (addr + 4 > memory_size) { throw std::runtime_error("Memory access out of bounds"); @@ -262,40 +268,52 @@ void VM::eval() { } break; } - case 0x6F: { // JAL - int32_t offset = ((instr & 0x80000000) ? 0xFFF00000 : 0) | // Sign-extension for imm[20] - ((instr >> 21) & 0x3FF) << 1 | // imm[10:1] - ((instr >> 20) & 0x1) << 11 | // imm[11] - ((instr & 0xFF000)); // imm[19:12] + case 0x6F: { // JAL + int32_t offset = + ((instr & 0x80000000) ? 0xFFF00000 + : 0) | // Sign-extension for imm[20] + ((instr >> 21) & 0x3FF) << 1 | // imm[10:1] + ((instr >> 20) & 0x1) << 11 | // imm[11] + ((instr & 0xFF000)); // imm[19:12] - registers[rd] = pc; // Save return address + registers[rd] = pc; // Save return address pc += offset - 4; break; } - case 0x67: { // JALR - int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate - uint32_t target = (registers[rs1] + offset) & ~1; // Target address (LSB cleared) + case 0x67: { // JALR + int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate + uint32_t target = + (registers[rs1] + offset) & ~1; // Target address (LSB cleared) - registers[rd] = pc; // Save return address + registers[rd] = pc; // Save return address pc = target; break; } - case 0x37: { // LUI - uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate - registers[rd] = imm << 12; // Shift the immediate to the upper 20 bits of the register + case 0x37: { // LUI + uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate + registers[rd] = + imm + << 12; // Shift the immediate to the upper 20 bits of the register break; } - case 0x17: { // AUIPC - uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate - registers[rd] = pc + (imm << 12); // Add the immediate (shifted left) to the current PC + case 0x17: { // AUIPC + uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate + registers[rd] = + pc + + (imm << 12); // Add the immediate (shifted left) to the current PC break; } - case 0x73: { // EBREAK - running = false; // Flag to stop the emulator + case 0x73: { // EBREAK + running = false; // Flag to stop the emulator break; } default: throw std::runtime_error("Unknown opcode"); - } + } +} + +void VM::eval() { + while (running) { + step(); } } diff --git a/src/vm.hpp b/src/vm.hpp index 571e9b0..092ffe0 100644 --- a/src/vm.hpp +++ b/src/vm.hpp @@ -1,26 +1,30 @@ #pragma once -#include #include +#include #include #include -const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers +const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers -std::vector load_program(const std::string& filename, size_t memory_size); +std::vector load_program(const std::string& filename, + size_t memory_size); class VM { -public: + public: VM(std::vector memory); + void step(); void eval(); std::vector read_memory(size_t start, size_t size); uint32_t read_register(size_t regnum); -private: + private: std::vector memory_; uint32_t registers[NUM_REGISTERS] = {0}; uint32_t pc = 0; + + bool running = true; };