Implement gdb single-stepping

This commit is contained in:
Konstantin Nazarov 2024-12-09 20:17:21 +00:00
parent 0175ef0c80
commit d0ff238cb0
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
3 changed files with 149 additions and 111 deletions

View file

@ -1,5 +1,7 @@
#include "debug.hpp" #include "debug.hpp"
#include <netinet/tcp.h>
GDBStub::GDBStub(VM &vm, int port) GDBStub::GDBStub(VM &vm, int port)
: server_fd(-1), client_fd(-1), running(false), vm(vm) { : server_fd(-1), client_fd(-1), running(false), vm(vm) {
server_fd = socket(AF_INET, SOCK_STREAM, 0); 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."); 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; std::cout << "GDB connected!" << std::endl;
running = true; running = true;
} }
@ -135,10 +143,13 @@ void GDBStub::handle_packet(const std::string &packet) {
// Getting specific register // Getting specific register
int regnum = std::stoi(packet.substr(1), nullptr, 16); 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; 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()); send_packet(response.str());
break; break;
@ -167,6 +178,7 @@ void GDBStub::handle_packet(const std::string &packet) {
break; break;
case 's': case 's':
vm.step();
send_packet("S05"); // TODO: step execution send_packet("S05"); // TODO: step execution
break; break;
@ -203,7 +215,11 @@ bool GDBStub::parse_memory_request(const std::string &packet, size_t &addr,
std::string GDBStub::read_registers() { std::string GDBStub::read_registers() {
std::ostringstream out; std::ostringstream out;
for (int i = 0; i < 32; ++i) { 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(); return out.str();
} }

View file

@ -2,22 +2,21 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <fstream>
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include <fstream>
inline int32_t sign_extend(int32_t value, int bits) { inline int32_t sign_extend(int32_t value, int bits) {
int32_t mask = 1 << (bits - 1); int32_t mask = 1 << (bits - 1);
return (value ^ mask) - mask; return (value ^ mask) - mask;
} }
std::vector<uint8_t> load_program(const std::string& filename, size_t memory_size) std::vector<uint8_t> load_program(const std::string& filename,
{ size_t memory_size) {
std::vector<uint8_t> memory(memory_size, 0); std::vector<uint8_t> 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()) { if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filename); throw std::runtime_error("Failed to open file: " + filename);
@ -34,7 +33,8 @@ std::vector<uint8_t> load_program(const std::string& filename, size_t memory_s
file.read(reinterpret_cast<char*>(&memory[0]), file_size); file.read(reinterpret_cast<char*>(&memory[0]), file_size);
if (!file) { 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(); file.close();
@ -42,17 +42,19 @@ std::vector<uint8_t> load_program(const std::string& filename, size_t memory_s
return memory; return memory;
} }
VM::VM(std::vector<uint8_t> memory) VM::VM(std::vector<uint8_t> memory) : memory_(memory) {}
: memory_(memory) {
}
std::vector<uint8_t> VM::read_memory(size_t start, size_t size) { std::vector<uint8_t> VM::read_memory(size_t start, size_t size) {
if (start + size > memory_.size()) {
return std::vector<uint8_t>(size, 0);
}
return std::vector<uint8_t>(memory_.begin() + start, return std::vector<uint8_t>(memory_.begin() + start,
memory_.begin() + start + size); memory_.begin() + start + size);
} }
uint32_t VM::read_register(size_t regnum) { uint32_t VM::read_register(size_t regnum) {
if (regnum == 32) return pc;
if (regnum >= NUM_REGISTERS) { if (regnum >= NUM_REGISTERS) {
throw std::runtime_error("Register out of range"); throw std::runtime_error("Register out of range");
} }
@ -60,15 +62,12 @@ uint32_t VM::read_register(size_t regnum) {
return registers[regnum]; return registers[regnum];
} }
void VM::eval() { void VM::step() {
size_t memory_size = memory_.size(); 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]; uint32_t instr = *(uint32_t*)&memory[pc];
if (instr == 0) break; // std::cout << "pc: " << std::hex << pc << std::dec << "\n";
//std::cout << "pc: " << std::hex << pc << std::dec << "\n";
// std::cout << "instr: " << std::hex << instr << "\n"; // std::cout << "instr: " << std::hex << instr << "\n";
pc += 4; pc += 4;
@ -99,7 +98,10 @@ void VM::eval() {
uint32_t shift_amount = registers[rs2] & 0x1F; uint32_t shift_amount = registers[rs2] & 0x1F;
registers[rd] = value << shift_amount; registers[rd] = value << shift_amount;
} else if (funct3 == 0x02) { // SLT } else if (funct3 == 0x02) { // SLT
registers[rd] = (static_cast<int32_t>(registers[rs1]) < static_cast<int32_t>(registers[rs2]))?0:1; registers[rd] = (static_cast<int32_t>(registers[rs1]) <
static_cast<int32_t>(registers[rs2]))
? 0
: 1;
} else if (funct3 == 0x03) { // SLTU } else if (funct3 == 0x03) { // SLTU
registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0; registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0;
} else { } else {
@ -118,15 +120,18 @@ void VM::eval() {
} }
} else if (funct7 == 0x01) { } else if (funct7 == 0x01) {
if (funct3 == 0x0) { // MUL if (funct3 == 0x0) { // MUL
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) * int64_t result =
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
static_cast<int64_t>(static_cast<int32_t>(registers[rs2])); static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
registers[rd] = static_cast<uint32_t>(result); registers[rd] = static_cast<uint32_t>(result);
} else if (funct3 == 0x1) { // MULH } else if (funct3 == 0x1) { // MULH
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) * int64_t result =
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
static_cast<int64_t>(static_cast<int32_t>(registers[rs2])); static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
registers[rd] = static_cast<uint32_t>(result >> 32); registers[rd] = static_cast<uint32_t>(result >> 32);
} else if (funct3 == 0x2) { // MULSU } else if (funct3 == 0x2) { // MULSU
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) * int64_t result =
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
static_cast<uint64_t>(registers[rs2]); static_cast<uint64_t>(registers[rs2]);
registers[rd] = static_cast<uint32_t>(result >> 32); registers[rd] = static_cast<uint32_t>(result >> 32);
} else if (funct3 == 0x3) { // MULU } else if (funct3 == 0x3) { // MULU
@ -151,7 +156,8 @@ void VM::eval() {
int32_t dividend = static_cast<int32_t>(registers[rs1]); int32_t dividend = static_cast<int32_t>(registers[rs1]);
int32_t divisor = static_cast<int32_t>(registers[rs2]); int32_t divisor = static_cast<int32_t>(registers[rs2]);
if (divisor == 0) { 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) { } else if (dividend == INT32_MIN && divisor == -1) {
registers[rd] = 0; // Overflow case registers[rd] = 0; // Overflow case
} else { } else {
@ -263,7 +269,9 @@ void VM::eval() {
break; break;
} }
case 0x6F: { // JAL case 0x6F: { // JAL
int32_t offset = ((instr & 0x80000000) ? 0xFFF00000 : 0) | // Sign-extension for imm[20] int32_t offset =
((instr & 0x80000000) ? 0xFFF00000
: 0) | // Sign-extension for imm[20]
((instr >> 21) & 0x3FF) << 1 | // imm[10:1] ((instr >> 21) & 0x3FF) << 1 | // imm[10:1]
((instr >> 20) & 0x1) << 11 | // imm[11] ((instr >> 20) & 0x1) << 11 | // imm[11]
((instr & 0xFF000)); // imm[19:12] ((instr & 0xFF000)); // imm[19:12]
@ -274,7 +282,8 @@ void VM::eval() {
} }
case 0x67: { // JALR case 0x67: { // JALR
int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate
uint32_t target = (registers[rs1] + offset) & ~1; // Target address (LSB cleared) 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; pc = target;
@ -282,12 +291,16 @@ void VM::eval() {
} }
case 0x37: { // LUI case 0x37: { // LUI
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate 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 registers[rd] =
imm
<< 12; // Shift the immediate to the upper 20 bits of the register
break; break;
} }
case 0x17: { // AUIPC case 0x17: { // AUIPC
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate
registers[rd] = pc + (imm << 12); // Add the immediate (shifted left) to the current PC registers[rd] =
pc +
(imm << 12); // Add the immediate (shifted left) to the current PC
break; break;
} }
case 0x73: { // EBREAK case 0x73: { // EBREAK
@ -297,5 +310,10 @@ void VM::eval() {
default: default:
throw std::runtime_error("Unknown opcode"); throw std::runtime_error("Unknown opcode");
} }
}
void VM::eval() {
while (running) {
step();
} }
} }

View file

@ -1,26 +1,30 @@
#pragma once #pragma once
#include <cstdint>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers
std::vector<uint8_t> load_program(const std::string& filename, size_t memory_size); std::vector<uint8_t> load_program(const std::string& filename,
size_t memory_size);
class VM { class VM {
public: public:
VM(std::vector<uint8_t> memory); VM(std::vector<uint8_t> memory);
void step();
void eval(); void eval();
std::vector<uint8_t> read_memory(size_t start, size_t size); std::vector<uint8_t> read_memory(size_t start, size_t size);
uint32_t read_register(size_t regnum); uint32_t read_register(size_t regnum);
private: private:
std::vector<uint8_t> memory_; std::vector<uint8_t> memory_;
uint32_t registers[NUM_REGISTERS] = {0}; uint32_t registers[NUM_REGISTERS] = {0};
uint32_t pc = 0; uint32_t pc = 0;
bool running = true;
}; };