Implement a GDB stub and fix another huge issue in S-type immediate decoding
This commit is contained in:
159
src/core.rs
159
src/core.rs
@@ -4,10 +4,14 @@
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{collections::HashSet, sync::mpsc};
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, RegId, RegValue},
|
||||
core::commands::CoreCmd,
|
||||
decode::Instruction,
|
||||
exceptions::ExceptionType,
|
||||
gdb::{self, DebugCommand, StopReason},
|
||||
instructions::find_and_exec,
|
||||
mem::MemConfig,
|
||||
};
|
||||
@@ -16,53 +20,154 @@ pub struct Core {
|
||||
pub(crate) x_regs: [RegValue; 32],
|
||||
pub(crate) pc: Addr,
|
||||
pub(crate) mem: MemConfig,
|
||||
command_stream: mpsc::Receiver<CoreCmd>,
|
||||
}
|
||||
|
||||
pub mod commands;
|
||||
|
||||
impl Core {
|
||||
pub fn new(mem: MemConfig) -> Self {
|
||||
pub fn new(mem: MemConfig, command_stream: mpsc::Receiver<CoreCmd>) -> Self {
|
||||
Self {
|
||||
x_regs: [0; 32],
|
||||
pc: 0,
|
||||
mem,
|
||||
command_stream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
self.throw_exception(ExceptionType::InstructionAddressMisaligned);
|
||||
break;
|
||||
if let Ok(cmd) = self.command_stream.try_recv() {
|
||||
match cmd {
|
||||
CoreCmd::EnterDbgMode(dbg_stream) => {
|
||||
let _ = self.debug_loop(dbg_stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let instr = match self.mem.read_word(self.pc) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
self.throw_exception(e.to_exception_instr());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if instr == 0 {
|
||||
self.throw_exception(ExceptionType::IllegalInstruction);
|
||||
break;
|
||||
}
|
||||
|
||||
if instr & 3 != 3 {
|
||||
// Compressed instruction - (currently) unsupported
|
||||
self.throw_exception(ExceptionType::IllegalInstruction);
|
||||
break;
|
||||
}
|
||||
|
||||
let instr = Instruction(instr);
|
||||
|
||||
if let Err(e) = find_and_exec(instr, self) {
|
||||
if let Err(e) = self.step() {
|
||||
self.throw_exception(e);
|
||||
eprintln!("instr: {:08x}", instr.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_waiting_for_cmd(&mut self) {
|
||||
eprintln!("Waiting for any core command...");
|
||||
if let Ok(cmd) = self.command_stream.recv() {
|
||||
eprintln!("Recieved a command");
|
||||
match cmd {
|
||||
CoreCmd::EnterDbgMode(dbg_stream) => {
|
||||
let _ = self.debug_loop(dbg_stream);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
eprintln!("Error recieving command, starting anyway");
|
||||
}
|
||||
|
||||
eprintln!("Command processed");
|
||||
|
||||
self.run();
|
||||
}
|
||||
|
||||
fn debug_loop(&mut self, dbg_stream: mpsc::Receiver<gdb::DebugCommand>) -> anyhow::Result<()> {
|
||||
let mut breakpoints = HashSet::new();
|
||||
|
||||
loop {
|
||||
match dbg_stream.recv()? {
|
||||
DebugCommand::GetRegs(sender) => sender.send(gdb::RegsResponse {
|
||||
x_regs: self.x_regs.clone(),
|
||||
pc: self.pc,
|
||||
})?,
|
||||
DebugCommand::ReadMem {
|
||||
addr,
|
||||
len,
|
||||
responder,
|
||||
} => {
|
||||
let data = (0..len)
|
||||
.map(|offset| self.mem.read_byte(addr + offset))
|
||||
.collect();
|
||||
|
||||
responder.send(data)?;
|
||||
}
|
||||
DebugCommand::SetBreakpoint(addr) => {
|
||||
breakpoints.insert(addr);
|
||||
}
|
||||
DebugCommand::RemoveBreakpoint(addr) => {
|
||||
breakpoints.remove(&addr);
|
||||
}
|
||||
DebugCommand::Step(responder) => {
|
||||
responder.send(match self.step() {
|
||||
Ok(_) => gdb::StopReason::Step,
|
||||
Err(e) => {
|
||||
self.throw_exception(e);
|
||||
gdb::StopReason::Exception(e)
|
||||
}
|
||||
})?;
|
||||
}
|
||||
DebugCommand::Continue(responder, stopper) => {
|
||||
responder.send(self.continue_loop(&breakpoints, stopper))?;
|
||||
}
|
||||
DebugCommand::ExitDebugMode => {
|
||||
eprintln!("exitdbgmode");
|
||||
break Ok(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn continue_loop(
|
||||
&mut self,
|
||||
breakpoints: &HashSet<Addr>,
|
||||
stopper: oneshot::Receiver<()>,
|
||||
) -> StopReason {
|
||||
loop {
|
||||
if breakpoints.contains(&self.pc) {
|
||||
return StopReason::Exception(ExceptionType::Breakpoint);
|
||||
}
|
||||
|
||||
if let Ok(_) = stopper.try_recv() {
|
||||
return StopReason::Interrupted;
|
||||
}
|
||||
|
||||
if let Err(e) = self.step() {
|
||||
self.throw_exception(e);
|
||||
return StopReason::Exception(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn step(&mut self) -> Result<(), ExceptionType> {
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
self.throw_exception(ExceptionType::InstructionAddressMisaligned);
|
||||
}
|
||||
|
||||
let instr = match self.mem.read_word(self.pc) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
return Err(e.to_exception_instr());
|
||||
}
|
||||
};
|
||||
|
||||
if instr == 0 {
|
||||
return Err(ExceptionType::IllegalInstruction);
|
||||
}
|
||||
|
||||
if instr & 3 != 3 {
|
||||
// Compressed instruction - (currently) unsupported
|
||||
return Err(ExceptionType::IllegalInstruction);
|
||||
}
|
||||
|
||||
let instr = Instruction(instr);
|
||||
|
||||
if let Err(e) = find_and_exec(instr, self) {
|
||||
eprintln!("instr: {:08x}", instr.0);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn throw_exception(&mut self, exception_type: ExceptionType) {
|
||||
eprintln!("Exception: {exception_type:?}");
|
||||
dbg!(self.pc, self.x_regs);
|
||||
|
||||
7
src/core/commands.rs
Normal file
7
src/core/commands.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use crate::gdb;
|
||||
|
||||
pub enum CoreCmd {
|
||||
EnterDbgMode(mpsc::Receiver<gdb::DebugCommand>),
|
||||
}
|
||||
@@ -57,7 +57,9 @@ impl Instruction {
|
||||
|
||||
#[inline]
|
||||
pub fn imm_s(self) -> DWord {
|
||||
(self.0 as i32 as i64 >> (25 - 5) & (0x7f << 5)) as DWord | (self.0 >> 7 & 0b11111) as DWord
|
||||
let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as DWord;
|
||||
let imm_4_0 = (self.0 >> 7 & 0x1f) as DWord;
|
||||
imm_11_5 | imm_4_0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -29,6 +29,7 @@ pub enum ExceptionType {
|
||||
HardwareError = 19,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MemoryExceptionType {
|
||||
AddressMisaligned,
|
||||
AccessFault,
|
||||
|
||||
315
src/gdb.rs
Normal file
315
src/gdb.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use std::{
|
||||
io::{self, BufRead, ErrorKind, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::mpsc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, RegValue},
|
||||
core::commands::CoreCmd,
|
||||
exceptions::{ExceptionType, MemoryExceptionType},
|
||||
};
|
||||
|
||||
pub(crate) enum DebugCommand {
|
||||
GetRegs(oneshot::Sender<RegsResponse>),
|
||||
ReadMem {
|
||||
addr: Addr,
|
||||
len: u64,
|
||||
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
|
||||
},
|
||||
Step(oneshot::Sender<StopReason>),
|
||||
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
|
||||
SetBreakpoint(Addr),
|
||||
RemoveBreakpoint(Addr),
|
||||
ExitDebugMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum StopReason {
|
||||
Exception(ExceptionType),
|
||||
Step,
|
||||
Interrupted,
|
||||
}
|
||||
|
||||
impl StopReason {
|
||||
fn to_rsp(self) -> &'static str {
|
||||
match self {
|
||||
StopReason::Step => "S05",
|
||||
StopReason::Exception(e) => {
|
||||
use ExceptionType::*;
|
||||
match e {
|
||||
IllegalInstruction => "S04",
|
||||
InstructionAddressMisaligned
|
||||
| InstructionAccessFault
|
||||
| InstructionPageFault
|
||||
| LoadAddressMisaligned
|
||||
| LoadAccessFault
|
||||
| LoadPageFault
|
||||
| StoreAmoAddressMisaligned
|
||||
| StoreAmoAccessFault
|
||||
| StoreAmoPageFault => "S0b",
|
||||
_ => "S05",
|
||||
}
|
||||
}
|
||||
StopReason::Interrupted => "S02",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RegsResponse {
|
||||
pub x_regs: [RegValue; 32],
|
||||
pub pc: Addr,
|
||||
}
|
||||
|
||||
pub fn run_stub(cmd_sender: mpsc::Sender<CoreCmd>) {
|
||||
std::thread::spawn(move || {
|
||||
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
|
||||
|
||||
for stream_res in listener.incoming() {
|
||||
if let Ok(stream) = stream_res {
|
||||
let (dbg_tx, dbg_rx) = mpsc::channel();
|
||||
|
||||
stream
|
||||
.set_nonblocking(true)
|
||||
.expect("Couldnt set TCP stream to nonblocking");
|
||||
|
||||
cmd_sender
|
||||
.send(CoreCmd::EnterDbgMode(dbg_rx))
|
||||
.expect("couldnt ask core to enter debug mode");
|
||||
|
||||
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_gdb_connection(
|
||||
gdb_stream: TcpStream,
|
||||
dbg_tx: mpsc::Sender<DebugCommand>,
|
||||
) -> io::Result<()> {
|
||||
eprintln!("gdb connected");
|
||||
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
|
||||
let mut writer = gdb_stream;
|
||||
|
||||
loop {
|
||||
match read_rsp_packet(&mut reader) {
|
||||
Ok(packet) => match handle_packet(
|
||||
&packet[..packet.len() - 1],
|
||||
&mut writer,
|
||||
&dbg_tx,
|
||||
&mut reader,
|
||||
) {
|
||||
Err(_) => {
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_rsp_packet<R: BufRead>(reader: &mut R) -> io::Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
// Wait for leading '$'
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
let n = reader.read(&mut byte)?;
|
||||
if n == 0 {
|
||||
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Disconnected"));
|
||||
}
|
||||
if byte[0] == b'$' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read until '#'
|
||||
reader.read_until(b'#', &mut buf)?;
|
||||
|
||||
let mut checksum = [0u8; 2];
|
||||
reader.read_exact(&mut checksum)?;
|
||||
|
||||
String::from_utf8(buf).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
fn handle_packet<W: Write, R: BufRead>(
|
||||
packet: &str,
|
||||
writer: &mut W,
|
||||
dbg_tx: &mpsc::Sender<DebugCommand>,
|
||||
reader: &mut R,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(b"+")?;
|
||||
if packet.is_empty() {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match &packet[0..1] {
|
||||
"?" => {
|
||||
send_packet("S05", writer)?;
|
||||
}
|
||||
|
||||
"g" => {
|
||||
let (regs_tx, regs_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::GetRegs(regs_tx)).unwrap();
|
||||
let regs = regs_rx.recv().unwrap();
|
||||
let mut hex = String::with_capacity(32 * 8 * 2 + 8 * 2);
|
||||
for ® in ®s.x_regs {
|
||||
hex.push_str(
|
||||
®.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
}
|
||||
hex.push_str(
|
||||
®s
|
||||
.pc
|
||||
.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
|
||||
"m" => {
|
||||
if let Some((addr_str, len_str)) = packet[1..].split_once(',') {
|
||||
if let (Ok(addr), Ok(len)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(len_str, 16),
|
||||
) {
|
||||
let (responder, response) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::ReadMem {
|
||||
addr,
|
||||
len,
|
||||
responder,
|
||||
})
|
||||
.unwrap();
|
||||
let response = response.recv().unwrap();
|
||||
|
||||
match response {
|
||||
Ok(data) => {
|
||||
let hex: String = data.iter().map(|b| format!("{b:02x}")).collect();
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
Err(e) => send_packet(&format!("E.{e:?}"), writer)?,
|
||||
};
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
"s" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(&stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"c" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
let (stopper, stop_listener) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::Continue(responder, stop_listener))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
match reader.read(&mut byte) {
|
||||
Ok(0) => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(1) if byte[0] == 0x03 => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
|
||||
send_packet(&stop_reason.to_rsp(), writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"Z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
|
||||
if let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
) {
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::SetBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
"z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
|
||||
if let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
) {
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::RemoveBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_packet<W: Write>(packet: &str, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(b"$")?;
|
||||
writer.write_all(packet.as_bytes())?;
|
||||
writer.write_all(b"#")?;
|
||||
let checksum = packet.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
|
||||
write!(writer, "{checksum:02x}")?;
|
||||
// eprintln!("successfully sent packet {packet:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,5 +2,6 @@ pub mod consts;
|
||||
pub mod core;
|
||||
mod decode;
|
||||
pub mod exceptions;
|
||||
pub mod gdb;
|
||||
mod instructions;
|
||||
pub mod mem;
|
||||
|
||||
38
src/main.rs
38
src/main.rs
@@ -4,33 +4,38 @@
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{env, sync::Arc, time::Duration};
|
||||
use std::{env, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use trve::{
|
||||
consts::{Addr, Byte, DWord, HWord, Word},
|
||||
core::Core,
|
||||
exceptions::MemoryExceptionType,
|
||||
gdb,
|
||||
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::basic_uart::BasicUart;
|
||||
|
||||
mod execload;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
executable: PathBuf,
|
||||
#[arg(long)]
|
||||
wait: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let mut ram = Ram::try_new(16 * 1024 * 1024)?;
|
||||
let buf = ram.buf_mut();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() != 2 {
|
||||
eprintln!("USAGE: trve <ram_image>");
|
||||
bail!("Wrong number of arguments");
|
||||
}
|
||||
|
||||
let entry_point = execload::load(&args[1], buf)?;
|
||||
let entry_point = execload::load(args.executable, buf)?;
|
||||
|
||||
let mut mmio_root = MmioRoot::default();
|
||||
mmio_root.insert(0, Arc::new(DbgOut));
|
||||
@@ -44,9 +49,18 @@ fn main() -> Result<()> {
|
||||
mmio_root,
|
||||
};
|
||||
|
||||
let mut core = Core::new(mem_cfg);
|
||||
let (cmd_sender, cmd_reciever) = std::sync::mpsc::channel();
|
||||
|
||||
gdb::run_stub(cmd_sender.clone());
|
||||
|
||||
let mut core = Core::new(mem_cfg, cmd_reciever);
|
||||
core.reset(entry_point);
|
||||
core.run();
|
||||
|
||||
if args.wait {
|
||||
core.run_waiting_for_cmd();
|
||||
} else {
|
||||
core.run();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user