Cleanup of the gdb stub
This commit is contained in:
parent
3d25d7f8f3
commit
aff44704f5
@ -17,7 +17,7 @@ impl Chip {
|
||||
log: log.clone(),
|
||||
cpu: CPU::new(log.clone()),
|
||||
rom: rom,
|
||||
ram: Box::new([0u8; 8 * 1024 + 1024]),
|
||||
ram: Box::new([0u8; 0x4000]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -119,8 +119,8 @@ impl CPU {
|
||||
}
|
||||
|
||||
fn ram_write(&self, ram: &mut [u8], addr: u16, val: u8) -> Result<(), CPUError> {
|
||||
// print!("[RAMW:{:04X}={:02X}] ", addr, val);
|
||||
if addr as usize >= ram.len() {
|
||||
error!(self.logger, "Ram write OOB: {:04X}={:02X}] ", addr, val);
|
||||
Err(CPUError::OutOfBoundsException)
|
||||
} else {
|
||||
if addr < 0x2000 {
|
||||
|
||||
453
src/gdbstub.rs
453
src/gdbstub.rs
@ -1,12 +1,12 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std;
|
||||
use std::net::TcpListener;
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use slog;
|
||||
|
||||
use chip;
|
||||
use cpu;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GDBPacket {
|
||||
@ -106,221 +106,272 @@ fn split_definitions(pkg: &str) -> Vec<(String, String)> {
|
||||
res
|
||||
}
|
||||
|
||||
/*
|
||||
struct GDBStub {
|
||||
chip: &mut chip::Chip,
|
||||
}
|
||||
*/
|
||||
|
||||
fn stop_reply(chip: &chip::Chip) -> Vec<u8> {
|
||||
// Send SIGTRAP along with SREG/SP/PC.
|
||||
format!("T0520:{:02X};21:{:04X};22:{:08X};",
|
||||
chip.cpu.sreg, chip.cpu.get_sp(&chip.ram).swap_bytes(),
|
||||
(2 * chip.cpu.pc).swap_bytes()
|
||||
).as_bytes().to_vec()
|
||||
struct GDBStub<'a> {
|
||||
log: slog::Logger,
|
||||
chip: &'a mut chip::Chip,
|
||||
breakpoints: HashSet<u32>,
|
||||
watchpoints_read: HashSet<u32>,
|
||||
watchpoints_write: HashSet<u32>,
|
||||
}
|
||||
|
||||
pub fn run(log: slog::Logger, chip: &mut chip::Chip) {
|
||||
let listener = TcpListener::bind("0.0.0.0:1234").unwrap();
|
||||
let mut conn = listener.incoming().nth(0).unwrap().unwrap();
|
||||
info!(log, "Debugger? attached.");
|
||||
impl<'a> GDBStub<'a> {
|
||||
fn stop_reply(&self) -> String {
|
||||
// Send SIGTRAP along with SREG/SP/PC.
|
||||
format!("T0520:{:02X};21:{:04X};22:{:08X};",
|
||||
self.chip.cpu.sreg,
|
||||
self.chip.cpu.get_sp(&self.chip.ram).swap_bytes(),
|
||||
(2 * self.chip.cpu.pc).swap_bytes()
|
||||
).to_string()
|
||||
}
|
||||
|
||||
let mut breakpoints: HashSet<u32> = HashSet::new();
|
||||
|
||||
loop {
|
||||
fn receive_pkg(&self, stream: &mut TcpStream) -> Result<(String, String), ()> {
|
||||
let mut buf = Vec::new();
|
||||
'a: loop {
|
||||
let mut has_checksum = false;
|
||||
let mut n_checksum_bytes = 0;
|
||||
let pkg;
|
||||
'rx_loop: loop {
|
||||
let mut s = [0u8];
|
||||
if conn.read(&mut s).unwrap() != 1 {
|
||||
if stream.read(&mut s).map_err(|_| ())? != 1 {
|
||||
panic!("Connection closed?")
|
||||
}
|
||||
if buf.len() == 0 && s[0] != b'$' {
|
||||
// Wait for beginning of pkg.
|
||||
continue;
|
||||
}
|
||||
// Wait for beginning of pkg.
|
||||
if buf.len() == 0 && s[0] != b'$' { continue; }
|
||||
buf.push(s[0]);
|
||||
match GDBPacket::from_packet(&buf) {
|
||||
Ok(_) => break 'a,
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
let pkg = GDBPacket::from_packet(&buf).unwrap();
|
||||
warn!(log, "<- {:?}", std::str::from_utf8(&pkg.raw_data));
|
||||
{
|
||||
let s = [b'+'];
|
||||
if conn.write(&s).unwrap() != 1 {
|
||||
panic!("Connection closed?");
|
||||
|
||||
if !has_checksum {
|
||||
has_checksum = s[0] == b'#';
|
||||
} else {
|
||||
n_checksum_bytes += 1;
|
||||
if n_checksum_bytes == 2 {
|
||||
// Can we parse the packet?
|
||||
match GDBPacket::from_packet(&buf) {
|
||||
Ok(p) => { pkg = Some(p); break 'rx_loop; },
|
||||
Err(_) => panic!("Could not parse pkg: {:?}", buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let pkg = pkg.unwrap();
|
||||
// Try to parse the string.
|
||||
let response = std::str::from_utf8(&pkg.raw_data)
|
||||
.map_err(|_| ())?.to_string();
|
||||
|
||||
let mut response = Vec::new();
|
||||
if pkg.raw_data[0] == b'q' {
|
||||
let query = std::str::from_utf8(&pkg.raw_data).unwrap();
|
||||
let query_data = split_definitions(&query[1..]);
|
||||
// TODO: Let's do this right :(
|
||||
if query_data[0].0 == "Supported" {
|
||||
response = "qXfer:memory-map:read+;PacketSize=1024".as_bytes().to_vec();
|
||||
} else if query_data[0].0 == "Attached" {
|
||||
response = "1".as_bytes().to_vec();
|
||||
} else if query_data[0].0 == "Xfer" {// && query_data[0].1.find("memory-map:read").is_some() {
|
||||
response = format!(
|
||||
"l<memory-map>\n<memory type='ram' start='0x800000' length='0x{:X}' />\n\
|
||||
<memory type='flash' start='0' length='0x{:X}'>\n\
|
||||
<property name='blocksize'>0x80</property>\n</memory></memory-map>", chip.rom.len(), chip.ram.len()
|
||||
).as_bytes().to_vec();
|
||||
} else if query_data[0].0 == "C" {
|
||||
response = "01".as_bytes().to_vec();
|
||||
} else {
|
||||
info!(log, "Unknown query: {}", query_data[0].0);
|
||||
}
|
||||
} else if pkg.raw_data[0] == b'H' {
|
||||
// Set thread - we only support one thread thus we ignore this.
|
||||
response = "OK".as_bytes().to_vec();
|
||||
} else if pkg.raw_data[0] == b'?' {
|
||||
// S05 = Stopped cause of SIGTRAP.
|
||||
response = "S05".as_bytes().to_vec();
|
||||
} else if pkg.raw_data[0] == b'g' {
|
||||
// Read registers.
|
||||
for i in 0..32 {
|
||||
response.extend(format!("{:02X}", chip.cpu.registers[i])
|
||||
.as_bytes().to_vec());
|
||||
}
|
||||
response.extend(format!("{:02X}", chip.cpu.sreg)
|
||||
.as_bytes().to_vec());
|
||||
response.extend(format!("{:04X}", chip.cpu.get_sp(&chip.ram).swap_bytes())
|
||||
.as_bytes().to_vec());
|
||||
response.extend(format!("{:08X}", (2 * chip.cpu.pc).swap_bytes())
|
||||
.as_bytes().to_vec());
|
||||
} else if pkg.raw_data[0] == b's' {
|
||||
let resume_from = std::str::from_utf8(&pkg.raw_data[1..])
|
||||
.unwrap_or("");
|
||||
let resume_from = u32::from_str_radix(resume_from, 16);
|
||||
if let Ok(pc) = resume_from {
|
||||
chip.cpu.pc = pc;
|
||||
}
|
||||
if !chip.step() {
|
||||
// ERR
|
||||
}
|
||||
response = stop_reply(&chip);
|
||||
} else if pkg.raw_data[0] == b'c' {
|
||||
let resume_from = std::str::from_utf8(&pkg.raw_data[1..])
|
||||
.unwrap_or("");
|
||||
let resume_from = u32::from_str_radix(resume_from, 16);
|
||||
if let Ok(pc) = resume_from {
|
||||
chip.cpu.pc = pc;
|
||||
}
|
||||
while chip.step() {
|
||||
// Maximum efficiency.
|
||||
if breakpoints.contains(&chip.cpu.pc) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
response = stop_reply(&chip);
|
||||
} else if pkg.raw_data[0] == b'm' {
|
||||
// Read memory
|
||||
let full_cmd = std::str::from_utf8(&pkg.raw_data).unwrap().chars().skip(1).collect::<String>();
|
||||
let parts = full_cmd.split(",").collect::<Vec<_>>();
|
||||
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap();
|
||||
let len = usize::from_str_radix(parts[1], 16).unwrap();
|
||||
for i in 0..len {
|
||||
// Copied from megumi again <3
|
||||
// GDB uses a special addressing to allow addressing of flash, SRAM, etc.
|
||||
// - flash starts at 0x00000000
|
||||
// - SRAM starts at 0x00800000
|
||||
// - mask for memory space is 0x00f00000
|
||||
// Constants used below are retrieved from GDB sources, in avr-tdep.c.
|
||||
let addr = mem_addr.wrapping_add(i);
|
||||
// TODO: Overflow checks.
|
||||
if addr & 0x00f00000 > 0 {
|
||||
// RAM
|
||||
response.extend(format!("{:02X}", chip.ram[addr & 0xFFFFF]).as_bytes().to_vec());
|
||||
} else {
|
||||
// ROM
|
||||
response.extend(format!("{:02X}", chip.rom[addr]).as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
} else if pkg.raw_data[0] == b'M' {
|
||||
// Write memory
|
||||
let full_cmd = std::str::from_utf8(&pkg.raw_data).unwrap().chars().skip(1).collect::<String>();
|
||||
let addrlen_content = full_cmd.split(":").collect::<Vec<_>>();
|
||||
let parts = addrlen_content[0].split(",").collect::<Vec<_>>();
|
||||
let value = addrlen_content[1];
|
||||
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap();
|
||||
let len = usize::from_str_radix(parts[1], 16).unwrap();
|
||||
for i in 0..len {
|
||||
// Copied from megumi again <3
|
||||
// GDB uses a special addressing to allow addressing of flash, SRAM, etc.
|
||||
// - flash starts at 0x00000000
|
||||
// - SRAM starts at 0x00800000
|
||||
// - mask for memory space is 0x00f00000
|
||||
// Constants used below are retrieved from GDB sources, in avr-tdep.c.
|
||||
let addr = mem_addr.wrapping_add(i);
|
||||
// Very hax wow
|
||||
if addr & 0x00f00000 > 0 {
|
||||
chip.ram[addr & 0xFFFFF] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap();
|
||||
} else {
|
||||
// ROM
|
||||
chip.rom[addr & 0xFFFFF] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap();
|
||||
}
|
||||
}
|
||||
} else if pkg.raw_data[0] == b'z' || pkg.raw_data[0] == b'Z' {
|
||||
// insert(Z)/remove(z) breakpoint.
|
||||
let d = std::str::from_utf8(&pkg.raw_data[1..]).unwrap();
|
||||
let values = d.split(",").collect::<Vec<_>>();
|
||||
let bp_type = values[0];
|
||||
let bp_addr = u32::from_str_radix(&values[1], 16).unwrap();
|
||||
let bp_kind = values[2];
|
||||
if pkg.raw_data[0] == b'z' {
|
||||
breakpoints.remove(&bp_addr);
|
||||
} else {
|
||||
breakpoints.insert(bp_addr);
|
||||
}
|
||||
response = "OK".as_bytes().to_vec();
|
||||
|
||||
} else if pkg.raw_data[0] == b'v' {
|
||||
let word = std::str::from_utf8(&pkg.raw_data[1..]).unwrap();
|
||||
let word_payload = word.split(";").collect::<Vec<_>>();
|
||||
let word = word_payload[0];
|
||||
if word == "MustReplyEmpty" {
|
||||
// Empty reply ;)
|
||||
}
|
||||
// Split it into command and values.
|
||||
if response.chars().nth(0).ok_or(())? == 'v' {
|
||||
// Multibyte word, up to the first ; (or others?).
|
||||
let word_payload = response.split(";").collect::<Vec<_>>();
|
||||
Ok((word_payload[0].to_string(),
|
||||
word_payload[1..].join(";").to_string()))
|
||||
} else {
|
||||
info!(log, "Unknown cmd: {}", pkg.raw_data[0]);
|
||||
// Unknown
|
||||
Ok((response.chars().nth(0).ok_or(())?.to_string(),
|
||||
response.chars().skip(1).collect::<String>()))
|
||||
}
|
||||
}
|
||||
|
||||
info!(log, "Send our response: {:?}", std::str::from_utf8(&response).unwrap());
|
||||
let response = GDBPacket { raw_data: response };
|
||||
conn.write_all(&response.to_packet_format()).unwrap();
|
||||
/*
|
||||
let r = cpu.step(&mut rom, &mut ram);
|
||||
match r {
|
||||
Ok(_) => {}
|
||||
Err(ref e) => {
|
||||
warn!(log, "Error occured: {:?}", e);
|
||||
break;
|
||||
}
|
||||
pub fn new(log: slog::Logger, chip: &'a mut chip::Chip) -> Self {
|
||||
Self {
|
||||
log: log,
|
||||
chip: chip,
|
||||
breakpoints: HashSet::new(),
|
||||
watchpoints_read: HashSet::new(),
|
||||
watchpoints_write: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, port: u16) {
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).unwrap();
|
||||
let mut conn = listener.incoming().nth(0).unwrap().unwrap();
|
||||
info!(self.log, "Debugger attached.");
|
||||
// Main debugging loop.
|
||||
loop {
|
||||
let (cmd, payload) = self.receive_pkg(&mut conn).unwrap();
|
||||
let response: Cow<str>;
|
||||
info!(self.log, "<- {} {}", cmd, payload);
|
||||
// Send ACK.
|
||||
conn.write_all(&"+".as_bytes()).unwrap();
|
||||
match &*cmd {
|
||||
"q" => {
|
||||
let query_data = split_definitions(&payload);
|
||||
match &*query_data[0].0 {
|
||||
"Supported" => {
|
||||
response = "qXfer:memory-map:read+;PacketSize=1024".into();
|
||||
},
|
||||
"Attached" => {
|
||||
response = "1".into();
|
||||
},
|
||||
"Xfer" => {
|
||||
response = format!(
|
||||
"l<memory-map>\n\
|
||||
<memory type='ram' start='0x800000' length='0x{:X}' />\n\
|
||||
<memory type='flash' start='0' length='0x{:X}'>\n\
|
||||
<property name='blocksize'>0x80</property>\n\
|
||||
</memory></memory-map>",
|
||||
self.chip.rom.len(), self.chip.ram.len()
|
||||
).to_string().into();
|
||||
},
|
||||
"C" => response = "01".into(),
|
||||
query => {
|
||||
warn!(self.log, "Unknown query: '{}'", query);
|
||||
response = "".into();
|
||||
},
|
||||
}
|
||||
}
|
||||
"H" => response = "OK".into(),
|
||||
"?" => response = "S05".into(),
|
||||
"g" => {
|
||||
// Read registers.
|
||||
let mut reg_vals = Vec::new();
|
||||
// R0-R31
|
||||
for i in 0..32 {
|
||||
reg_vals.push(
|
||||
format!("{:02X}", self.chip.cpu.registers[i]));
|
||||
}
|
||||
// SREG
|
||||
reg_vals.push(format!("{:02X}", self.chip.cpu.sreg));
|
||||
// SP
|
||||
reg_vals.push(
|
||||
format!(
|
||||
"{:04X}",
|
||||
self.chip.cpu.get_sp(&self.chip.ram).swap_bytes()
|
||||
)
|
||||
);
|
||||
// PC
|
||||
reg_vals.push(
|
||||
format!("{:08X}", (2 * self.chip.cpu.pc).swap_bytes()));
|
||||
|
||||
response = reg_vals.join("").into();
|
||||
},
|
||||
"s" | "c" => {
|
||||
// Step / Continue
|
||||
// Parse resume from.
|
||||
let resume_from = u32::from_str_radix(&*payload, 16);
|
||||
if let Ok(pc) = resume_from {
|
||||
self.chip.cpu.pc = pc;
|
||||
}
|
||||
if cmd == "s" {
|
||||
// Single step.
|
||||
self.chip.step();
|
||||
} else {
|
||||
// Continue.
|
||||
// It would be nice to have a look at the network
|
||||
// traffic here, maybe gdb wants to interrupt us.
|
||||
while self.chip.step() {
|
||||
// Check for breakpoints.
|
||||
// TODO: Watchpoints & stuff.
|
||||
if self.breakpoints.contains(&self.chip.cpu.pc) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send a stop reply.
|
||||
response = self.stop_reply().into();
|
||||
},
|
||||
"m" => {
|
||||
// Read memory.
|
||||
let parts = payload.split(",").collect::<Vec<_>>();
|
||||
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap_or(0);
|
||||
let len = usize::from_str_radix(parts[1], 16).unwrap_or(0);
|
||||
let mut data = Vec::new();
|
||||
let mut err = false;
|
||||
for i in 0..len {
|
||||
let addr = mem_addr.wrapping_add(i);
|
||||
// TODO: Overflow checks.
|
||||
if addr & 0x00f00000 > 0 {
|
||||
// RAM:
|
||||
let addr_i = addr & 0xFFFFF;
|
||||
if addr_i >= self.chip.ram.len() {
|
||||
// Partial read case.
|
||||
if data.len() == 0 {
|
||||
err = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
data.push(format!("{:02X}", self.chip.ram[addr_i]));
|
||||
} else {
|
||||
// ROM
|
||||
if addr >= self.chip.rom.len() {
|
||||
// Partial read case.
|
||||
if data.len() == 0 {
|
||||
err = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
data.push(format!("{:02X}", self.chip.rom[addr]));
|
||||
}
|
||||
}
|
||||
if err {
|
||||
response = "E05".into(); // TODO: Which error would be correct?
|
||||
} else {
|
||||
response = data.join("").into();
|
||||
}
|
||||
},
|
||||
"M" => {
|
||||
// Write memory.
|
||||
let addrlen_content = payload.split(":").collect::<Vec<_>>();
|
||||
let parts = addrlen_content[0].split(",").collect::<Vec<_>>();
|
||||
let value = addrlen_content[1];
|
||||
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap();
|
||||
let len = usize::from_str_radix(parts[1], 16).unwrap();
|
||||
let mut err = false;
|
||||
for i in 0..len {
|
||||
let addr = mem_addr.wrapping_add(i);
|
||||
// TODO: Overflow checks.
|
||||
if addr & 0x00f00000 > 0 {
|
||||
// RAM:
|
||||
let addr_i = addr & 0xFFFFF;
|
||||
if addr_i >= self.chip.ram.len() {
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
self.chip.ram[addr_i] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap_or(0);
|
||||
} else {
|
||||
// ROM
|
||||
if addr >= self.chip.rom.len() {
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
self.chip.rom[addr] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap_or(0);
|
||||
}
|
||||
}
|
||||
if err {
|
||||
response = "E05".into(); // TODO: Which error would be correct?
|
||||
} else {
|
||||
response = "OK".into();
|
||||
}
|
||||
},
|
||||
"z" | "Z" => {
|
||||
// insert(Z)/remove(z) breakpoint.
|
||||
let values = payload.split(",").collect::<Vec<_>>();
|
||||
let bp_type = values[0];
|
||||
let bp_addr = u32::from_str_radix(&values[1], 16).unwrap();
|
||||
let _bp_length = values[2];
|
||||
if bp_type == "0" || bp_type == "1" && bp_addr & 1 == 0 {
|
||||
let cpu_addr = bp_addr >> 1;
|
||||
if cmd == "z" {
|
||||
self.breakpoints.remove(&cpu_addr);
|
||||
} else {
|
||||
self.breakpoints.insert(cpu_addr);
|
||||
}
|
||||
info!(self.log, "New BP state: {:?}", self.breakpoints);
|
||||
response = "OK".into();
|
||||
} else {
|
||||
response = "E05".into(); // TODO: Which error would be correct?
|
||||
}
|
||||
},
|
||||
"vMustReplyEmpty" => response = "".into(),
|
||||
_ => {
|
||||
info!(self.log, "Unknown cmd: {} {}", cmd, payload);
|
||||
response = "".into();
|
||||
}
|
||||
}
|
||||
info!(self.log, "-> {}", response);
|
||||
let response = GDBPacket { raw_data: response.as_bytes().to_vec() };
|
||||
conn.write_all(&response.to_packet_format()).unwrap();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn main() {
|
||||
println!("Sample pkg: {:?}", String::from_utf8_lossy(&GDBPacket { raw_data: vec![]}.to_packet_format()));
|
||||
let pkg = GDBPacket::from_packet(&"$#00".as_bytes().iter().map(|v| *v).collect::<Vec<_>>()).expect("Could not parse pkg");
|
||||
|
||||
// Handler for g: Read general registers.
|
||||
// Each byte of register data is described by two hex digits. The bytes with the register are transmitted in target byte order. The size of each register and their position within the ‘g’ packet are determined by the GDB internal gdbarch functions DEPRECATED_REGISTER_RAW_SIZE and gdbarch_register_name.
|
||||
// From megumi: 35 value,s total 39 bytes
|
||||
// 0-31: Regular regs
|
||||
// 33 SREG
|
||||
// 34-35 SP
|
||||
// 36-39 PC
|
||||
let get_reg_response = GDBPacket { raw_data: [0u8; 39].to_vec() };
|
||||
// G: same but set
|
||||
// m M
|
||||
// m: addr:hex,len:hex
|
||||
// s c
|
||||
pub fn run(log: slog::Logger, chip: &mut chip::Chip) {
|
||||
let mut debugger = GDBStub::new(log, chip);
|
||||
debugger.run(1234);
|
||||
}
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user