Cleanup of the gdb stub

This commit is contained in:
Kevin Hamacher 2018-02-16 19:43:49 +01:00
parent 3d25d7f8f3
commit aff44704f5
3 changed files with 254 additions and 203 deletions

View File

@ -17,7 +17,7 @@ impl Chip {
log: log.clone(), log: log.clone(),
cpu: CPU::new(log.clone()), cpu: CPU::new(log.clone()),
rom: rom, rom: rom,
ram: Box::new([0u8; 8 * 1024 + 1024]), ram: Box::new([0u8; 0x4000]),
} }
} }

View File

@ -119,8 +119,8 @@ impl CPU {
} }
fn ram_write(&self, ram: &mut [u8], addr: u16, val: u8) -> Result<(), CPUError> { 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() { if addr as usize >= ram.len() {
error!(self.logger, "Ram write OOB: {:04X}={:02X}] ", addr, val);
Err(CPUError::OutOfBoundsException) Err(CPUError::OutOfBoundsException)
} else { } else {
if addr < 0x2000 { if addr < 0x2000 {

View File

@ -1,12 +1,12 @@
use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std; use std;
use std::net::TcpListener; use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write}; use std::io::{Read, Write};
use slog; use slog;
use chip; use chip;
use cpu;
#[derive(Debug)] #[derive(Debug)]
pub struct GDBPacket { pub struct GDBPacket {
@ -106,221 +106,272 @@ fn split_definitions(pkg: &str) -> Vec<(String, String)> {
res res
} }
/* struct GDBStub<'a> {
struct GDBStub { log: slog::Logger,
chip: &mut chip::Chip, chip: &'a mut chip::Chip,
breakpoints: HashSet<u32>,
watchpoints_read: HashSet<u32>,
watchpoints_write: HashSet<u32>,
} }
*/
fn stop_reply(chip: &chip::Chip) -> Vec<u8> { impl<'a> GDBStub<'a> {
fn stop_reply(&self) -> String {
// Send SIGTRAP along with SREG/SP/PC. // Send SIGTRAP along with SREG/SP/PC.
format!("T0520:{:02X};21:{:04X};22:{:08X};", format!("T0520:{:02X};21:{:04X};22:{:08X};",
chip.cpu.sreg, chip.cpu.get_sp(&chip.ram).swap_bytes(), self.chip.cpu.sreg,
(2 * chip.cpu.pc).swap_bytes() self.chip.cpu.get_sp(&self.chip.ram).swap_bytes(),
).as_bytes().to_vec() (2 * self.chip.cpu.pc).swap_bytes()
).to_string()
} }
pub fn run(log: slog::Logger, chip: &mut chip::Chip) { fn receive_pkg(&self, stream: &mut TcpStream) -> Result<(String, String), ()> {
let listener = TcpListener::bind("0.0.0.0:1234").unwrap();
let mut conn = listener.incoming().nth(0).unwrap().unwrap();
info!(log, "Debugger? attached.");
let mut breakpoints: HashSet<u32> = HashSet::new();
loop {
let mut buf = Vec::new(); 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]; let mut s = [0u8];
if conn.read(&mut s).unwrap() != 1 { if stream.read(&mut s).map_err(|_| ())? != 1 {
panic!("Connection closed?") panic!("Connection closed?")
} }
if buf.len() == 0 && s[0] != b'$' {
// Wait for beginning of pkg. // Wait for beginning of pkg.
continue; if buf.len() == 0 && s[0] != b'$' { continue; }
}
buf.push(s[0]); buf.push(s[0]);
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) { match GDBPacket::from_packet(&buf) {
Ok(_) => break 'a, Ok(p) => { pkg = Some(p); break 'rx_loop; },
Err(_) => {} Err(_) => panic!("Could not parse pkg: {:?}", buf),
} }
} }
let pkg = GDBPacket::from_packet(&buf).unwrap(); }
warn!(log, "<- {:?}", std::str::from_utf8(&pkg.raw_data)); }
{ let pkg = pkg.unwrap();
let s = [b'+']; // Try to parse the string.
if conn.write(&s).unwrap() != 1 { let response = std::str::from_utf8(&pkg.raw_data)
panic!("Connection closed?"); .map_err(|_| ())?.to_string();
// 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 {
Ok((response.chars().nth(0).ok_or(())?.to_string(),
response.chars().skip(1).collect::<String>()))
} }
} }
let mut response = Vec::new(); pub fn new(log: slog::Logger, chip: &'a mut chip::Chip) -> Self {
if pkg.raw_data[0] == b'q' { Self {
let query = std::str::from_utf8(&pkg.raw_data).unwrap(); log: log,
let query_data = split_definitions(&query[1..]); chip: chip,
// TODO: Let's do this right :( breakpoints: HashSet::new(),
if query_data[0].0 == "Supported" { watchpoints_read: HashSet::new(),
response = "qXfer:memory-map:read+;PacketSize=1024".as_bytes().to_vec(); watchpoints_write: HashSet::new(),
} 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() {
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!( response = format!(
"l<memory-map>\n<memory type='ram' start='0x800000' length='0x{:X}' />\n\ "l<memory-map>\n\
<memory type='ram' start='0x800000' length='0x{:X}' />\n\
<memory type='flash' start='0' 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() <property name='blocksize'>0x80</property>\n\
).as_bytes().to_vec(); </memory></memory-map>",
} else if query_data[0].0 == "C" { self.chip.rom.len(), self.chip.ram.len()
response = "01".as_bytes().to_vec(); ).to_string().into();
} else { },
info!(log, "Unknown query: {}", query_data[0].0); "C" => response = "01".into(),
query => {
warn!(self.log, "Unknown query: '{}'", query);
response = "".into();
},
} }
} else if pkg.raw_data[0] == b'H' { }
// Set thread - we only support one thread thus we ignore this. "H" => response = "OK".into(),
response = "OK".as_bytes().to_vec(); "?" => response = "S05".into(),
} else if pkg.raw_data[0] == b'?' { "g" => {
// S05 = Stopped cause of SIGTRAP.
response = "S05".as_bytes().to_vec();
} else if pkg.raw_data[0] == b'g' {
// Read registers. // Read registers.
let mut reg_vals = Vec::new();
// R0-R31
for i in 0..32 { for i in 0..32 {
response.extend(format!("{:02X}", chip.cpu.registers[i]) reg_vals.push(
.as_bytes().to_vec()); format!("{:02X}", self.chip.cpu.registers[i]));
} }
response.extend(format!("{:02X}", chip.cpu.sreg) // SREG
.as_bytes().to_vec()); reg_vals.push(format!("{:02X}", self.chip.cpu.sreg));
response.extend(format!("{:04X}", chip.cpu.get_sp(&chip.ram).swap_bytes()) // SP
.as_bytes().to_vec()); reg_vals.push(
response.extend(format!("{:08X}", (2 * chip.cpu.pc).swap_bytes()) format!(
.as_bytes().to_vec()); "{:04X}",
} else if pkg.raw_data[0] == b's' { self.chip.cpu.get_sp(&self.chip.ram).swap_bytes()
let resume_from = std::str::from_utf8(&pkg.raw_data[1..]) )
.unwrap_or(""); );
let resume_from = u32::from_str_radix(resume_from, 16); // 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 { if let Ok(pc) = resume_from {
chip.cpu.pc = pc; self.chip.cpu.pc = pc;
} }
if !chip.step() { if cmd == "s" {
// ERR // Single step.
} self.chip.step();
response = stop_reply(&chip); } else {
} else if pkg.raw_data[0] == b'c' { // Continue.
let resume_from = std::str::from_utf8(&pkg.raw_data[1..]) // It would be nice to have a look at the network
.unwrap_or(""); // traffic here, maybe gdb wants to interrupt us.
let resume_from = u32::from_str_radix(resume_from, 16); while self.chip.step() {
if let Ok(pc) = resume_from { // Check for breakpoints.
chip.cpu.pc = pc; // TODO: Watchpoints & stuff.
} if self.breakpoints.contains(&self.chip.cpu.pc) {
while chip.step() {
// Maximum efficiency.
if breakpoints.contains(&chip.cpu.pc) {
break; break;
} }
} }
response = stop_reply(&chip); }
} else if pkg.raw_data[0] == b'm' { // Send a stop reply.
// Read memory response = self.stop_reply().into();
let full_cmd = std::str::from_utf8(&pkg.raw_data).unwrap().chars().skip(1).collect::<String>(); },
let parts = full_cmd.split(",").collect::<Vec<_>>(); "m" => {
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap(); // Read memory.
let len = usize::from_str_radix(parts[1], 16).unwrap(); 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 { 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); let addr = mem_addr.wrapping_add(i);
// TODO: Overflow checks. // TODO: Overflow checks.
if addr & 0x00f00000 > 0 { if addr & 0x00f00000 > 0 {
// RAM // RAM:
response.extend(format!("{:02X}", chip.ram[addr & 0xFFFFF]).as_bytes().to_vec()); 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 { } else {
// ROM // ROM
response.extend(format!("{:02X}", chip.rom[addr]).as_bytes().to_vec()); if addr >= self.chip.rom.len() {
// Partial read case.
if data.len() == 0 {
err = true;
}
break;
}
data.push(format!("{:02X}", self.chip.rom[addr]));
} }
} }
} else if pkg.raw_data[0] == b'M' { if err {
// Write memory response = "E05".into(); // TODO: Which error would be correct?
let full_cmd = std::str::from_utf8(&pkg.raw_data).unwrap().chars().skip(1).collect::<String>(); } else {
let addrlen_content = full_cmd.split(":").collect::<Vec<_>>(); response = data.join("").into();
}
},
"M" => {
// Write memory.
let addrlen_content = payload.split(":").collect::<Vec<_>>();
let parts = addrlen_content[0].split(",").collect::<Vec<_>>(); let parts = addrlen_content[0].split(",").collect::<Vec<_>>();
let value = addrlen_content[1]; let value = addrlen_content[1];
let mem_addr = usize::from_str_radix(parts[0], 16).unwrap(); let mem_addr = usize::from_str_radix(parts[0], 16).unwrap();
let len = usize::from_str_radix(parts[1], 16).unwrap(); let len = usize::from_str_radix(parts[1], 16).unwrap();
let mut err = false;
for i in 0..len { 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); let addr = mem_addr.wrapping_add(i);
// Very hax wow // TODO: Overflow checks.
if addr & 0x00f00000 > 0 { if addr & 0x00f00000 > 0 {
chip.ram[addr & 0xFFFFF] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap(); // RAM:
} else { let addr_i = addr & 0xFFFFF;
// ROM if addr_i >= self.chip.ram.len() {
chip.rom[addr & 0xFFFFF] = u8::from_str_radix(&value[2 * i..2 * (i + 1)], 16).unwrap(); err = true;
}
}
} 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 ;)
}
} else {
info!(log, "Unknown cmd: {}", pkg.raw_data[0]);
// Unknown
}
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; 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();
} }
*/
} }
} }
/* pub fn run(log: slog::Logger, chip: &mut chip::Chip) {
fn main() { let mut debugger = GDBStub::new(log, chip);
println!("Sample pkg: {:?}", String::from_utf8_lossy(&GDBPacket { raw_data: vec![]}.to_packet_format())); debugger.run(1234);
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
} }
*/