extern crate byteorder;
use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;
use super::riscv::{RiscvCpu, RiscvCpuError};
use wishbone_bridge::{Bridge, BridgeError};
use log::{debug, error, info};
use crate::gdb::byteorder::ByteOrder;
use byteorder::{BigEndian, NativeEndian};
const SUPPORTED_QUERIES: &[u8] = b"PacketSize=3fff;qXfer:features:read+;qXfer:threads:read+;qXfer:memory-map:read-;QStartNoAckMode+;vContSupported+";
pub struct GdbController {
connection: TcpStream,
}
impl Write for GdbController {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.connection.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.connection.flush()
}
}
impl GdbController {
pub fn gdb_send(&mut self, inp: &[u8]) -> io::Result<()> {
let mut buffer = [0; 16388];
let mut checksum: u8 = 0;
buffer[0] = b'$';
for i in 0..inp.len() {
buffer[i + 1] = inp[i];
checksum = checksum.wrapping_add(inp[i]);
}
let checksum_str = &format!("{:02x}", checksum);
let checksum_bytes = checksum_str.as_bytes();
buffer[inp.len() + 1] = b'#';
buffer[inp.len() + 2] = checksum_bytes[0];
buffer[inp.len() + 3] = checksum_bytes[1];
let (to_write, _rest) = buffer.split_at(inp.len() + 4);
debug!(
" > Writing {} bytes: {}",
to_write.len(),
String::from_utf8_lossy(&to_write)
);
self.connection.write_all(&to_write)?;
Ok(())
}
pub fn print_string(&mut self, msg: &str) -> io::Result<()> {
debug!("Printing string {} to GDB", msg);
let mut strs: Vec<String> = msg
.as_bytes()
.iter()
.map(|b| format!("{:02X}", b))
.collect();
strs.insert(0, "O".to_string());
let joined = strs.join("");
self.gdb_send(joined.as_bytes())
}
}
pub struct GdbServer {
connection: TcpStream,
no_ack_mode: bool,
is_alive: bool,
last_signal: u8,
}
fn swab(src: u32) -> u32 {
(src << 24) & 0xff00_0000
| (src << 8) & 0x00ff_0000
| (src >> 8) & 0x0000_ff00
| (src >> 24) & 0x0000_00ff
}
pub fn parse_u32(value: &str) -> Result<u32, GdbServerError> {
match u32::from_str_radix(value, 16) {
Ok(o) => Ok(o),
Err(e) => Err(GdbServerError::NumberParseError(value.to_owned(), e)),
}
}
pub fn parse_i32(value: &str) -> Result<i32, GdbServerError> {
match i32::from_str_radix(value, 16) {
Ok(o) => Ok(o),
Err(e) => Err(GdbServerError::NumberParseError(value.to_owned(), e)),
}
}
fn gdb_unescape(input: &[u8]) -> Vec<u8> {
let mut out = input.to_vec();
out.iter_mut().fold(&mut Vec::new(), |vec_acc, this_u8| {
if vec_acc.last() == Some(&(b'}')) {
let len = vec_acc.len();
vec_acc[len - 1] = *this_u8 ^ 0x20;
} else {
vec_acc.push(*this_u8);
}
vec_acc
});
out
}
pub fn parse_u64(value: &str) -> Result<u64, GdbServerError> {
match u64::from_str_radix(value, 16) {
Ok(o) => Ok(o),
Err(e) => Err(GdbServerError::NumberParseError(value.to_owned(), e)),
}
}
#[derive(Debug)]
pub enum GdbServerError {
IoError(io::Error),
ConnectionClosed,
NumberParseError(String, std::num::ParseIntError),
CpuError(RiscvCpuError),
BridgeError(BridgeError),
ProtocolError,
UnknownBreakpointType(String),
}
impl std::convert::From<BridgeError> for GdbServerError {
fn from(e: BridgeError) -> Self {
GdbServerError::BridgeError(e)
}
}
impl std::convert::From<RiscvCpuError> for GdbServerError {
fn from(e: RiscvCpuError) -> Self {
GdbServerError::CpuError(e)
}
}
impl std::convert::From<io::Error> for GdbServerError {
fn from(e: io::Error) -> Self {
GdbServerError::IoError(e)
}
}
#[derive(Debug, PartialEq)]
pub enum BreakPointType {
BreakSoft,
BreakHard,
WatchWrite,
WatchRead,
WatchAccess,
}
impl BreakPointType {
fn from_str(r: &str) -> Result<BreakPointType, GdbServerError> {
match r {
"0" => Ok(BreakPointType::BreakSoft),
"1" => Ok(BreakPointType::BreakHard),
"2" => Ok(BreakPointType::WatchWrite),
"3" => Ok(BreakPointType::WatchRead),
"4" => Ok(BreakPointType::WatchAccess),
c => Err(GdbServerError::UnknownBreakpointType(c.to_string())),
}
}
}
#[derive(Debug, PartialEq)]
pub enum GdbCommand {
Unknown(String),
MustReplyEmpty,
SupportedQueries(String),
StartNoAckMode,
Disconnect,
SetCurrentThread(u64),
ContinueThread(i32),
LastSignalPacket,
GetThreadInfo,
GetCurrentThreadId,
CheckIsAttached,
GetRegisters,
GetRegister(u32),
SetRegister(u32, u32),
SymbolsReady,
ReadMemory(u32 , u32 ),
WriteMemory(
u32,
u32,
Vec<u32>,
),
VContQuery,
VContContinue,
VContContinueFromSignal(String),
VContStepFromSignal(String),
Continue,
Step,
Interrupt,
MonitorCommand(String),
AddBreakpoint(
BreakPointType,
u32,
u32,
),
RemoveBreakpoint(
BreakPointType,
u32,
u32,
),
GetOffsets,
ReadMemoryMap(u32 , u32 ),
ReadFeature(
String,
u32,
u32,
),
TraceStatusQuery,
ReadThreads(u32 , u32 ),
}
impl GdbServer {
pub fn new(connection: TcpStream) -> Result<GdbServer, GdbServerError> {
Ok(GdbServer {
connection,
no_ack_mode: false,
is_alive: true,
last_signal: 0,
})
}
#[allow(clippy::cognitive_complexity)]
fn packet_to_command(&self, raw_pkt: &[u8]) -> Result<GdbCommand, GdbServerError> {
let pkt = String::from_utf8_lossy(raw_pkt).to_string();
debug!("Raw GDB packet: {}", pkt);
if pkt == "qSupported" || pkt.starts_with("qSupported:") {
Ok(GdbCommand::SupportedQueries(pkt))
} else if pkt == "D" {
Ok(GdbCommand::Disconnect)
} else if pkt == "QStartNoAckMode" {
Ok(GdbCommand::StartNoAckMode)
} else if pkt == "qAttached" {
Ok(GdbCommand::CheckIsAttached)
} else if pkt == "qOffsets" {
Ok(GdbCommand::GetOffsets)
} else if pkt == "qTStatus" {
Ok(GdbCommand::TraceStatusQuery)
} else if pkt.starts_with("qXfer:memory-map:read::") {
let pkt = pkt.trim_start_matches("qXfer:memory-map:read::");
let offsets: Vec<&str> = pkt.split(',').collect();
let offset = parse_u32(offsets[0])?;
let len = parse_u32(offsets[1])?;
Ok(GdbCommand::ReadMemoryMap(offset, len))
} else if pkt.starts_with("qXfer:features:read:") {
let pkt = pkt.trim_start_matches("qXfer:features:read:");
let fields: Vec<&str> = pkt.split(':').collect();
let offsets: Vec<&str> = fields[1].split(',').collect();
let offset = parse_u32(offsets[0])?;
let len = parse_u32(offsets[1])?;
Ok(GdbCommand::ReadFeature(fields[0].to_string(), offset, len))
} else if pkt.starts_with("qXfer:threads:read::") {
let pkt = pkt.trim_start_matches("qXfer:threads:read::");
let offsets: Vec<&str> = pkt.split(',').collect();
let offset = parse_u32(offsets[0])?;
let len = parse_u32(offsets[1])?;
Ok(GdbCommand::ReadThreads(offset, len))
} else if pkt.starts_with('Z') {
let pkt = pkt.trim_start_matches('Z');
let fields: Vec<&str> = pkt.split(',').collect();
let bptype = BreakPointType::from_str(fields[0])?;
let address = parse_u32(fields[1])?;
let size = parse_u32(fields[2])?;
Ok(GdbCommand::AddBreakpoint(bptype, address, size))
} else if pkt.starts_with('z') {
let pkt = pkt.trim_start_matches('z');
let fields: Vec<&str> = pkt.split(',').collect();
let bptype = BreakPointType::from_str(fields[0])?;
let address = parse_u32(fields[1])?;
let size = parse_u32(fields[2])?;
Ok(GdbCommand::RemoveBreakpoint(bptype, address, size))
} else if pkt.starts_with("qRcmd,") {
let pkt = pkt.trim_start_matches("qRcmd,");
let pkt_bytes = pkt.as_bytes();
let mut tmp1 = Vec::new();
let mut acc = 0;
for (i, pkt_byte) in pkt_bytes.iter().enumerate() {
let nybble = if *pkt_byte >= 0x30 && *pkt_byte <= 0x39 {
*pkt_byte - 0x30
} else if *pkt_byte >= 0x61 && *pkt_byte <= 0x66 {
*pkt_byte + 10 - 0x61
} else if *pkt_byte >= 0x41 && *pkt_byte <= 0x46 {
*pkt_byte + 10 - 0x41
} else {
0
};
if i & 1 == 1 {
tmp1.push((acc << 4) | nybble);
acc = 0;
} else {
acc = nybble;
}
}
Ok(GdbCommand::MonitorCommand(
String::from_utf8_lossy(&tmp1).to_string(),
))
} else if pkt == "g" {
Ok(GdbCommand::GetRegisters)
} else if pkt.starts_with('P') {
let pkt = pkt.trim_start_matches('P').to_string();
let v: Vec<&str> = pkt.split('=').collect();
let addr = parse_u32(v[0])?;
let value = swab(parse_u32(v[1])?);
Ok(GdbCommand::SetRegister(addr, value))
} else if pkt == "c" {
Ok(GdbCommand::Continue)
} else if pkt == "s" {
Ok(GdbCommand::Step)
} else if pkt.starts_with('m') {
let pkt = pkt.trim_start_matches('m').to_string();
let v: Vec<&str> = pkt.split(',').collect();
let addr = parse_u32(v[0])?;
let length = parse_u32(v[1])?;
Ok(GdbCommand::ReadMemory(addr, length))
} else if pkt.starts_with('M') {
let pkt = pkt.trim_start_matches('M').to_string();
let d: Vec<&str> = pkt.split(':').collect();
let v: Vec<&str> = d[0].split(',').collect();
let addr = parse_u32(v[0])?;
let length = parse_u32(v[1])?;
let value = swab(parse_u32(d[1])?);
Ok(GdbCommand::WriteMemory(addr, length, vec![value]))
} else if pkt.starts_with('X') {
let (_opcode, data) = match raw_pkt.split_first() {
None => return Err(GdbServerError::ProtocolError),
Some(s) => s,
};
let mut delimiter_offset = None;
for (idx, c) in data.iter().enumerate() {
if *c == b':' {
delimiter_offset = Some(idx);
break;
}
}
let delimiter_offset = match delimiter_offset {
Some(s) => s,
None => return Err(GdbServerError::ProtocolError),
};
let (description, bin_data_plus) = data.split_at(delimiter_offset);
let bin_data_plus = bin_data_plus.split_first();
let description = String::from_utf8_lossy(&description).to_string();
let v: Vec<&str> = description.split(',').collect();
let addr = parse_u32(v[0])?;
let length = parse_u32(v[1])?;
let mut values = vec![];
if let Some((_delimiter, bin_data)) = bin_data_plus {
let bin_data = gdb_unescape(bin_data);
for value in bin_data.chunks_exact(4) {
values.push(swab(BigEndian::read_u32(&value)));
}
let remainder = bin_data.chunks_exact(4).remainder();
if !remainder.is_empty() {
let mut remainder = remainder.to_vec();
while remainder.len() < 4 {
remainder.insert(0, 0);
}
values.push(swab(BigEndian::read_u32(&remainder)));
}
}
Ok(GdbCommand::WriteMemory(addr, length, values))
} else if pkt.starts_with('p') {
Ok(GdbCommand::GetRegister(parse_u32(
pkt.trim_start_matches('p'),
)?))
} else if pkt.starts_with("Hg") {
Ok(GdbCommand::SetCurrentThread(parse_u64(
pkt.trim_start_matches("Hg"),
)?))
} else if pkt.starts_with("Hc") {
Ok(GdbCommand::ContinueThread(parse_i32(
pkt.trim_start_matches("Hc"),
)?))
} else if pkt == "qC" {
Ok(GdbCommand::GetCurrentThreadId)
} else if pkt == "?" {
Ok(GdbCommand::LastSignalPacket)
} else if pkt == "qfThreadInfo" {
Ok(GdbCommand::GetThreadInfo)
} else if pkt == "vCont?" {
Ok(GdbCommand::VContQuery)
} else if pkt == "vCont;c" || pkt == "vCont;c:0" {
Ok(GdbCommand::VContContinue)
} else if pkt.starts_with("vCont;C") {
let pkt = pkt.trim_start_matches("vCont;C").to_string();
Ok(GdbCommand::VContContinueFromSignal(pkt))
} else if pkt.starts_with("vCont;s") {
let pkt = pkt.trim_start_matches("vCont;s").to_string();
Ok(GdbCommand::VContStepFromSignal(pkt))
} else if pkt == "qSymbol::" {
Ok(GdbCommand::SymbolsReady)
} else if pkt == "vMustReplyEmpty" {
Ok(GdbCommand::MustReplyEmpty)
} else {
info!("unrecognized GDB command: {}", pkt);
Ok(GdbCommand::Unknown(pkt))
}
}
pub fn get_controller(&self) -> GdbController {
GdbController {
connection: self.connection.try_clone().unwrap(),
}
}
pub fn get_command(&mut self) -> Result<GdbCommand, GdbServerError> {
let cmd = self.do_get_command()?;
debug!("< GDB packet: {:?}", cmd);
Ok(cmd)
}
fn do_get_command(&mut self) -> Result<GdbCommand, GdbServerError> {
let mut buffer = [0; 16384];
let mut byte = [0; 1];
let mut remote_checksum = [0; 2];
let mut buffer_offset = 0;
loop {
let len = self.connection.read(&mut byte)?;
if len == 0 {
return Err(GdbServerError::ConnectionClosed);
}
match byte[0] {
0x24 => {
let mut checksum: u8 = 0;
loop {
let len = self.connection.read(&mut byte)?;
if len == 0 {
return Err(GdbServerError::ConnectionClosed);
}
match byte[0] as char {
'#' => {
self.connection.read_exact(&mut remote_checksum)?;
let checksum_str = format!("{:02x}", checksum);
if checksum_str != String::from_utf8_lossy(&remote_checksum) {
info!(
"Checksum mismatch: Calculated {:?} vs {}",
checksum_str,
String::from_utf8_lossy(&remote_checksum)
);
self.gdb_send_nak()?;
} else if !self.no_ack_mode {
self.gdb_send_ack()?;
}
let (buffer, _remainder) = buffer.split_at(buffer_offset);
return self.packet_to_command(&buffer);
}
other => {
buffer[buffer_offset] = other as u8;
buffer_offset += 1;
checksum = checksum.wrapping_add(other as u8);
}
}
}
}
0x2b => {}
0x2d => {}
0x3 => return Ok(GdbCommand::Interrupt),
other => error!("Warning: unrecognied byte received: {}", other),
}
}
}
#[allow(clippy::cognitive_complexity)]
pub fn process(
&mut self,
cmd: GdbCommand,
cpu: &RiscvCpu,
bridge: &Bridge,
) -> Result<(), GdbServerError> {
match cmd {
GdbCommand::SupportedQueries(_) => self.gdb_send(SUPPORTED_QUERIES)?,
GdbCommand::StartNoAckMode => {
self.no_ack_mode = true;
self.gdb_send(b"OK")?
}
GdbCommand::SetCurrentThread(_) => self.gdb_send(b"OK")?,
GdbCommand::ContinueThread(_) => self.gdb_send(b"OK")?,
GdbCommand::AddBreakpoint(_bptype, address, _size) => {
let response = match cpu.add_breakpoint(bridge, address) {
Ok(_) => "OK",
Err(RiscvCpuError::BreakpointExhausted) => {
error!("No available breakpoint found");
"E0E"
}
Err(e) => {
error!(
"An error occurred while trying to add the breakpoint: {:?}",
e
);
"E0E"
}
};
self.gdb_send(response.as_bytes())?;
}
GdbCommand::TraceStatusQuery => self.gdb_send(b"")?,
GdbCommand::RemoveBreakpoint(_bptype, address, _size) => {
cpu.remove_breakpoint(bridge, address)?;
self.gdb_send(b"OK")?
}
GdbCommand::LastSignalPacket => {
let sig_str = format!("S{:02x}", self.last_signal);
self.gdb_send(if self.is_alive {
sig_str.as_bytes()
} else {
b"W00"
})?
}
GdbCommand::GetThreadInfo => self.gdb_send(b"l")?,
GdbCommand::GetCurrentThreadId => self.gdb_send(b"QC0")?,
GdbCommand::CheckIsAttached => self.gdb_send(b"1")?,
GdbCommand::Disconnect => {
cpu.resume(bridge)?;
self.gdb_send(b"OK")?
}
GdbCommand::GetRegisters => {
let mut register_list = String::new();
for i in cpu.all_cpu_registers() {
register_list
.push_str(format!("{:08x}", swab(cpu.read_register(bridge, i)?)).as_str());
}
self.gdb_send(register_list.as_bytes())?
}
GdbCommand::GetRegister(reg) => {
let response = match cpu.read_register(bridge, reg) {
Ok(val) => format!("{:08x}", swab(val)),
Err(e) => {
error!("Error reading register: {}", e);
"E01".to_string()
}
};
self.gdb_send(response.as_bytes())?
}
GdbCommand::SetRegister(reg, val) => {
let response = match cpu.write_register(bridge, reg, val) {
Ok(()) => "OK",
Err(_) => "E01",
};
self.gdb_send(response.as_bytes())?
}
GdbCommand::SymbolsReady => self.gdb_send(b"OK")?,
GdbCommand::ReadMemory(addr, len) => {
debug!("Reading memory {:08x}", addr);
let mut values = vec![];
let mut out_str = String::new();
if len == 1 {
let val = cpu.read_memory(bridge, addr, 1)? as u8;
out_str.push_str(&format!("{:02x}", val));
self.gdb_send(out_str.as_bytes())?
} else if len == 2 {
let val = cpu.read_memory(bridge, addr, 2)? as u16;
let mut buf = [0; 2];
BigEndian::write_u16(&mut buf, val);
out_str.push_str(&format!("{:04x}", NativeEndian::read_u16(&buf)));
self.gdb_send(out_str.as_bytes())?
} else if len == 4 {
values.push(cpu.read_memory(bridge, addr, 4)?);
self.gdb_send_u32(values)?
} else {
for offset in (0..len).step_by(4) {
values.push(cpu.read_memory(bridge, addr + offset, 4)?);
if addr + offset >= 0xffff_fffc {
break;
}
}
self.gdb_send_u32(values)?
}
}
GdbCommand::WriteMemory(addr, len, values) => {
if len == 1 {
debug!("Writing memory {:08x} -> {:08x}", addr, values[0] >> 24);
cpu.write_memory(bridge, addr, 1, values[0] >> 24)?;
} else if len == 2 {
debug!("Writing memory {:08x} -> {:08x}", addr, values[0] >> 16);
cpu.write_memory(bridge, addr, 2, values[0] >> 16)?;
} else if len == 4 {
debug!("Writing memory {:08x} -> {:08x}", addr, values[0]);
cpu.write_memory(bridge, addr, 4, values[0])?;
} else {
for (offset, value) in values.iter().enumerate() {
debug!("Writing memory {:08x} -> {:08x}", addr, values[offset]);
cpu.write_memory(bridge, addr + (offset as u32 * 4), 4, *value)?;
}
}
self.gdb_send(b"OK")?
}
GdbCommand::VContQuery => self.gdb_send(b"vCont;c;C;s;S")?,
GdbCommand::VContContinue => {
if let Some(s) = cpu.resume(bridge)? {
self.print_string(&format!("Note: CPU is currently in a trap: {}\n", s))?
}
}
GdbCommand::VContContinueFromSignal(_) => {
if let Some(s) = cpu.resume(bridge)? {
self.print_string(&format!("Note: CPU is currently in a trap: {}\n", s))?
}
}
GdbCommand::VContStepFromSignal(_) => {
if let Some(s) = cpu.step(bridge)? {
self.print_string(&format!("Note: CPU is currently in a trap: {}\n", s))?;
}
self.last_signal = 5;
self.gdb_send(format!("S{:02x}", self.last_signal).as_bytes())?;
}
GdbCommand::GetOffsets => self.gdb_send(b"Text=0;Data=0;Bss=0")?,
GdbCommand::Continue => {
if let Some(s) = cpu.resume(bridge)? {
self.print_string(&format!("Note: CPU is currently in a trap: {}\n", s))?
}
}
GdbCommand::Step => {
if let Some(s) = cpu.step(bridge)? {
self.print_string(&format!("Note: CPU is currently in a trap: {}\n", s))?
}
}
GdbCommand::MonitorCommand(cmd) => {
match cmd.as_str() {
"reset" => {
self.print_string("Resetting CPU...\n")?;
cpu.reset(&bridge)?;
}
"about" => {
self.print_string("VexRiscv GDB bridge\n")?;
}
"explain" => {
self.print_string(&cpu.explain(&bridge)?)?;
}
_ => {
self.print_string("Unrecognized monitor command. Available commands:\n")?;
self.print_string(" about - Information about the bridge\n")?;
self.print_string(" explain - Explain what the CPU is doing\n")?;
self.print_string(" reset - Reset the CPU\n")?;
}
}
self.gdb_send(b"OK")?
}
GdbCommand::ReadFeature(filename, offset, len) => {
self.gdb_send_file(cpu.get_feature(&filename)?, offset, len)?
}
GdbCommand::ReadMemoryMap(_offset, _len) => {
self.gdb_send(b"")?
}
GdbCommand::ReadThreads(offset, len) => {
self.gdb_send_file(cpu.get_threads()?, offset, len)?
}
GdbCommand::Interrupt => {
self.last_signal = 2;
cpu.halt(bridge)?;
self.gdb_send(format!("S{:02x}", self.last_signal).as_bytes())?;
}
GdbCommand::MustReplyEmpty => self.gdb_send(b"")?,
GdbCommand::Unknown(_) => self.gdb_send(b"")?,
};
Ok(())
}
fn gdb_send_ack(&mut self) -> io::Result<usize> {
self.connection.write(&[b'+'])
}
fn gdb_send_nak(&mut self) -> io::Result<usize> {
self.connection.write(&[b'-'])
}
fn gdb_send_u32(&mut self, vals: Vec<u32>) -> io::Result<()> {
let mut out_str = String::new();
for val in vals {
let mut buf = [0; 4];
BigEndian::write_u32(&mut buf, val);
out_str.push_str(&format!("{:08x}", NativeEndian::read_u32(&buf)));
}
self.gdb_send(out_str.as_bytes())
}
fn gdb_send(&mut self, inp: &[u8]) -> io::Result<()> {
let mut buffer = [0; 16388];
let mut checksum: u8 = 0;
buffer[0] = b'$';
for i in 0..inp.len() {
buffer[i + 1] = inp[i];
checksum = checksum.wrapping_add(inp[i]);
}
let checksum_str = &format!("{:02x}", checksum);
let checksum_bytes = checksum_str.as_bytes();
buffer[inp.len() + 1] = b'#';
buffer[inp.len() + 2] = checksum_bytes[0];
buffer[inp.len() + 3] = checksum_bytes[1];
let (to_write, _rest) = buffer.split_at(inp.len() + 4);
self.connection.write_all(&to_write)?;
Ok(())
}
pub fn print_string(&mut self, msg: &str) -> io::Result<()> {
debug!("Printing string {} to GDB", msg);
let mut strs: Vec<String> = msg
.as_bytes()
.iter()
.map(|b| format!("{:02X}", b))
.collect();
strs.insert(0, "O".to_string());
let joined = strs.join("");
self.gdb_send(joined.as_bytes())
}
fn gdb_send_file(&mut self, mut data: Vec<u8>, offset: u32, len: u32) -> io::Result<()> {
let offset = offset as usize;
let len = len as usize;
let mut end = offset + len;
if offset > data.len() {
self.gdb_send(b"l")?;
} else {
if end > data.len() {
end = data.len();
}
let mut trimmed_data: Vec<u8> = data.drain(offset..end).collect();
if trimmed_data.len() >= len {
trimmed_data.insert(0, b'm');
} else {
trimmed_data.insert(0, b'l');
}
self.gdb_send(&trimmed_data)?;
}
Ok(())
}
}