#![deny(missing_docs)]
#![allow(clippy::enum_variant_names, clippy::result_unit_err)]
use log::{debug, trace};
use nom::*;
use strum_macros::*;
use nom::IResult::*;
use nom::{IResult, Needed};
use std::borrow::Cow;
use std::cmp::min;
use std::convert::From;
use std::io::{self, Read, Write};
use std::ops::Range;
use std::str::{self, FromStr};
mod fs;
#[cfg(feature = "libc")]
mod libc_fs;
mod sigs;
pub use self::fs::{FileSystem, HostErrno, HostMode, HostOpenFlags, IOResult};
#[cfg(feature = "libc")]
pub use self::libc_fs::LibcFS;
pub use self::sigs::Signal;
use self::fs::{write_stat, HostStat};
const MAX_PACKET_SIZE: usize = 64 * 1024;
named!(checksum<&[u8], u8>,
map_res!(map_res!(take!(2), str::from_utf8),
|s| u8::from_str_radix(s, 16)));
named!(packet<&[u8], (Vec<u8>, u8)>,
preceded!(tag!("$"),
separated_pair!(map!(opt!(is_not!("#")), |o: Option<&[u8]>| {
o.map_or(vec!(), |s| s.to_vec())
}),
tag!("#"),
checksum)));
#[derive(Debug, PartialEq, Eq)]
enum Packet {
Ack,
Nack,
Interrupt,
Data(Vec<u8>, u8),
}
named!(
packet_or_response<Packet>,
alt!(
packet => { |(d, chk)| Packet::Data(d, chk) }
| tag!("+") => { |_| Packet::Ack }
| tag!("-") => { |_| Packet::Nack }
| tag!("\x03") => { |_| Packet::Interrupt }
)
);
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug, EnumString, PartialEq)]
enum GDBFeature {
multiprocess,
xmlRegisters,
qRelocInsn,
swbreak,
hwbreak,
#[strum(serialize = "fork-events")]
fork_events,
#[strum(serialize = "vfork-events")]
vfork_events,
#[strum(serialize = "exec-events")]
exec_events,
vContSupported,
#[strum(serialize = "no-resumed")]
no_resumed,
QThreadEvents,
}
#[derive(Clone, Debug, PartialEq)]
enum Known<'a> {
Yes(GDBFeature),
No(&'a str),
}
#[derive(Clone, Debug, PartialEq)]
struct GDBFeatureSupported<'a>(Known<'a>, FeatureSupported<'a>);
#[derive(Clone, Debug, PartialEq)]
enum FeatureSupported<'a> {
Yes,
No,
#[allow(unused)]
Maybe,
Value(&'a str),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SetThreadFor {
Continue,
ReadRegister,
}
#[derive(Clone, Debug, PartialEq)]
enum Query<'a> {
Attached(Option<u64>),
CurrentThread,
SearchMemory { address: u64, length: u64, bytes: Vec<u8> },
SupportedFeatures(Vec<GDBFeatureSupported<'a>>),
StartNoAckMode,
Invoke(Vec<u8>),
AddressRandomization(bool),
CatchSyscalls(Option<Vec<u64>>),
PassSignals(Vec<u64>),
ProgramSignals(Vec<u64>),
ThreadInfo(ThreadId),
ThreadList(bool),
ReadBytes {
object: String,
annex: String,
offset: u64,
length: u64,
},
#[cfg(feature = "lldb")]
RegisterInfo(u64),
#[cfg(feature = "lldb")]
ProcessInfo,
Symbol(String, String),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Id {
Id(u32),
All,
Any,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ThreadId {
pub pid: Id,
pub tid: Id,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Watchpoint {
pub addr: u64,
pub n_bytes: u64,
}
impl Watchpoint {
fn new(addr: u64, n_bytes: u64) -> Watchpoint {
Watchpoint { addr, n_bytes }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Bytecode {
pub bytecode: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Breakpoint {
pub addr: u64,
pub kind: u64,
pub conditions: Option<Vec<Bytecode>>,
pub commands: Option<Vec<Bytecode>>,
}
impl Breakpoint {
fn new(addr: u64, kind: u64, conditions: Option<Vec<Bytecode>>, commands: Option<Vec<Bytecode>>) -> Breakpoint {
Breakpoint {
addr,
kind,
conditions,
commands,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MemoryRegion {
pub address: u64,
pub length: u64,
}
impl MemoryRegion {
fn new(address: u64, length: u64) -> MemoryRegion {
MemoryRegion { address, length }
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VContFeature {
Continue = b'c',
ContinueWithSignal = b'C',
Step = b's',
StepWithSignal = b'S',
Stop = b't',
RangeStep = b'r',
}
#[derive(Clone, Debug, PartialEq)]
pub enum VCont {
Continue,
ContinueWithSignal(u8),
Step,
StepWithSignal(u8),
Stop,
RangeStep(Range<u64>),
}
#[derive(Clone, Debug, PartialEq)]
enum Command<'a> {
Detach(Option<u64>),
EnableExtendedMode,
TargetHaltReason,
ReadGeneralRegisters,
WriteGeneralRegisters(Vec<u8>),
ReadRegister(u64),
WriteRegister(u64, Vec<u8>),
Kill(Option<u64>),
ReadMemory(MemoryRegion),
WriteMemory(MemoryRegion, Vec<u8>),
Query(Query<'a>),
#[cfg(feature = "all_stop")]
Continue,
#[cfg(feature = "all_stop")]
Step,
Reset,
PingThread(ThreadId),
CtrlC,
UnknownVCommand,
SetCurrentThread(SetThreadFor, ThreadId),
InsertSoftwareBreakpoint(Breakpoint),
InsertHardwareBreakpoint(Breakpoint),
InsertWriteWatchpoint(Watchpoint),
InsertReadWatchpoint(Watchpoint),
InsertAccessWatchpoint(Watchpoint),
RemoveSoftwareBreakpoint(Breakpoint),
RemoveHardwareBreakpoint(Breakpoint),
RemoveWriteWatchpoint(Watchpoint),
RemoveReadWatchpoint(Watchpoint),
RemoveAccessWatchpoint(Watchpoint),
VContSupported,
VCont(Vec<(VCont, Option<ThreadId>)>),
HostOpen(Vec<u8>, u64, u64),
HostClose(u64),
HostPRead(u64, u64, u64),
HostPWrite(u64, u64, Vec<u8>),
HostFStat(u64),
HostUnlink(Vec<u8>),
HostReadlink(Vec<u8>),
HostSetFS(u64),
}
named!(
gdbfeature<Known<'_>>,
map!(map_res!(is_not_s!(";="), str::from_utf8), |s| {
match GDBFeature::from_str(s) {
Ok(f) => Known::Yes(f),
Err(_) => Known::No(s),
}
})
);
fn gdbfeaturesupported<'a>(i: &'a [u8]) -> IResult<&'a [u8], GDBFeatureSupported<'a>> {
flat_map!(i, is_not!(";"), |f: &'a [u8]| {
match f.split_last() {
None => IResult::Incomplete(Needed::Size(2)),
Some((&b'+', first)) => map!(first, gdbfeature, |feat| GDBFeatureSupported(
feat,
FeatureSupported::Yes
)),
Some((&b'-', first)) => map!(first, gdbfeature, |feat| GDBFeatureSupported(
feat,
FeatureSupported::No
)),
Some((_, _)) => map!(
f,
separated_pair!(gdbfeature, tag!("="), map_res!(is_not!(";"), str::from_utf8)),
|(feat, value)| GDBFeatureSupported(feat, FeatureSupported::Value(value))
),
}
})
}
named!(q_search_memory<&[u8], (u64, u64, Vec<u8>)>,
complete!(do_parse!(
tag!("qSearch:memory:") >>
address: hex_value >>
tag!(";") >>
length: hex_value >>
tag!(";") >>
data: hex_byte_sequence >>
(address, length, data))));
#[cfg(feature = "lldb")]
fn lldb_query(i: &[u8]) -> IResult<&[u8], Query<'_>> {
alt!(i,
tag!("qProcessInfo") => { |_| Query::ProcessInfo }
| preceded!(tag!("qRegisterInfo"), hex_value) => {
|reg| Query::RegisterInfo(reg)
}
)
}
#[cfg(not(feature = "lldb"))]
fn lldb_query(_i: &[u8]) -> IResult<&[u8], Query<'_>> {
IResult::Error(error_position!(ErrorKind::Alt, _i))
}
fn query<'a>(i: &'a [u8]) -> IResult<&'a [u8], Query<'a>> {
alt_complete!(i,
tag!("qC") => { |_| Query::CurrentThread }
| preceded!(tag!("qSupported"),
preceded!(tag!(":"),
separated_list_complete!(tag!(";"),
gdbfeaturesupported))) => {
|features: Vec<GDBFeatureSupported<'a>>| Query::SupportedFeatures(features)
}
| preceded!(tag!("qRcmd,"), hex_byte_sequence) => {
|bytes| Query::Invoke(bytes)
}
| q_search_memory => {
|(address, length, bytes)| Query::SearchMemory { address, length, bytes }
}
| tag!("QStartNoAckMode") => { |_| Query::StartNoAckMode }
| preceded!(tag!("qAttached:"), hex_value) => {
|value| Query::Attached(Some(value))
}
| tag!("qAttached") => { |_| Query::Attached(None) }
| tag!("qfThreadInfo") => { |_| Query::ThreadList(true) }
| tag!("qsThreadInfo") => { |_| Query::ThreadList(false) }
| tag!("QDisableRandomization:0") => { |_| Query::AddressRandomization(true) }
| tag!("QDisableRandomization:1") => { |_| Query::AddressRandomization(false) }
| tag!("QCatchSyscalls:0") => { |_| Query::CatchSyscalls(None) }
| preceded!(tag!("QCatchSyscalls:1"),
many0!(preceded!(tag!(";"), hex_value))) => {
|syscalls| Query::CatchSyscalls(Some(syscalls))
}
| preceded!(tag!("QPassSignals:"),
separated_list_complete!(tag!(";"), hex_value)) => {
|signals| Query::PassSignals(signals)
}
| preceded!(tag!("QProgramSignals:"),
separated_nonempty_list_complete!(tag!(";"), hex_value)) => {
|signals| Query::ProgramSignals(signals)
}
| preceded!(tag!("qThreadExtraInfo,"), parse_thread_id) => {
|thread_id| Query::ThreadInfo(thread_id)
}
| tuple!(
preceded!(tag!("qSymbol:"), map_res!(take_until!(":"), str::from_utf8)),
preceded!(tag!(":"), map_res!(eof!(), str::from_utf8))) => {
|(sym_value, sym_name): (&str, &str)| Query::Symbol(sym_value.to_owned(), sym_name.to_owned())
}
| tuple!(
preceded!(tag!("qXfer:"), map_res!(take_until!(":"), str::from_utf8)),
preceded!(tag!(":read:"), map_res!(take_until!(":"), str::from_utf8)),
preceded!(tag!(":"), hex_value),
preceded!(tag!(","), hex_value)) => {
|(object, annex, offset, length): (&str, &str, u64, u64)| Query::ReadBytes {
object: object.to_owned(),
annex: annex.to_owned(),
offset,
length,
}
}
| lldb_query => {
|q| q
}
)
}
named!(hex_value<&[u8], u64>,
map!(take_while1!(&nom::is_hex_digit),
|hex| {
let s = str::from_utf8(hex).unwrap();
let r = u64::from_str_radix(s, 16);
r.unwrap()
}));
named!(hex_digit<&[u8], char>,
one_of!("0123456789abcdefABCDEF"));
named!(hex_byte<&[u8], u8>,
do_parse!(
digit0: hex_digit >>
digit1: hex_digit >>
((16 * digit0.to_digit(16).unwrap() + digit1.to_digit(16).unwrap()) as u8)
)
);
named!(hex_byte_sequence<&[u8], Vec<u8>>,
many1!(hex_byte));
named!(write_memory<&[u8], (u64, u64, Vec<u8>)>,
complete!(do_parse!(
tag!("M") >>
address: hex_value >>
tag!(",") >>
length: hex_value >>
tag!(":") >>
data: hex_byte_sequence >>
(address, length, data))));
named!(binary_byte<&[u8], u8>,
alt_complete!(
preceded!(tag!("}"), take!(1)) => { |b: &[u8]| b[0] ^ 0x20 } |
take!(1) => { |b: &[u8]| b[0] }));
named!(binary_byte_sequence<&[u8], Vec<u8>>,
many1!(binary_byte));
named!(write_memory_binary<&[u8], (u64, u64, Vec<u8>)>,
complete!(do_parse!(
tag!("X") >>
address: hex_value >>
tag!(",") >>
length: hex_value >>
tag!(":") >>
data: binary_byte_sequence >>
(address, length, data))));
named!(read_memory<&[u8], (u64, u64)>,
preceded!(tag!("m"),
separated_pair!(hex_value,
tag!(","),
hex_value)));
named!(read_register<&[u8], u64>,
preceded!(tag!("p"), hex_value));
named!(write_register<&[u8], (u64, Vec<u8>)>,
preceded!(tag!("P"),
separated_pair!(hex_value,
tag!("="),
hex_byte_sequence)));
named!(write_general_registers<&[u8], Vec<u8>>,
preceded!(tag!("G"), hex_byte_sequence));
named!(parse_thread_id_element<&[u8], Id>,
alt_complete!(tag!("0") => { |_| Id::Any }
| tag!("-1") => { |_| Id::All }
| hex_value => { |val: u64| Id::Id(val as u32) }));
named!(parse_thread_id<&[u8], ThreadId>,
alt_complete!(parse_thread_id_element => { |pid| ThreadId { pid, tid: Id::Any } }
| preceded!(tag!("p"),
separated_pair!(parse_thread_id_element,
tag!("."),
parse_thread_id_element)) => {
|pair: (Id, Id)| ThreadId { pid: pair.0, tid: pair.1 }
}
| preceded!(tag!("p"), parse_thread_id_element) => {
|id: Id| ThreadId { pid: id, tid: Id::All }
}));
named!(parse_ping_thread<&[u8], ThreadId>,
preceded!(tag!("T"), parse_thread_id));
fn parse_vfile_open(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("open:")
>> filename: hex_byte_sequence
>> tag!(",")
>> flags: hex_value
>> tag!(",")
>> mode: hex_value
>> (Command::HostOpen(filename, flags, mode))
)
}
fn parse_vfile_close(i: &[u8]) -> IResult<&[u8], Command> {
do_parse!(
i,
tag!("close:") >> fd: hex_value >> (Command::HostClose(fd))
)
}
fn parse_vfile_pread(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("pread:")
>> fd: hex_value
>> tag!(",")
>> count: hex_value
>> tag!(",")
>> offset: hex_value
>> (Command::HostPRead(fd, count, offset))
)
}
fn parse_vfile_pwrite(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("pwrite:")
>> fd: hex_value
>> tag!(",")
>> offset: hex_value
>> tag!(",")
>> data: binary_byte_sequence
>> (Command::HostPWrite(fd, offset, data))
)
}
fn parse_vfile_fstat(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("fstat:") >> fd: hex_value >> (Command::HostFStat(fd))
)
}
fn parse_vfile_unlink(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("unlink:") >> filename: hex_byte_sequence >> (Command::HostUnlink(filename))
)
}
fn parse_vfile_readlink(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("readlink:") >> filename: hex_byte_sequence >> (Command::HostReadlink(filename))
)
}
fn parse_vfile_setfs(i: &[u8]) -> IResult<&[u8], Command<'_>> {
do_parse!(
i,
tag!("setfs:") >> pid: hex_value >> (Command::HostSetFS(pid))
)
}
fn parse_unknown_vfile_op(i: &[u8]) -> IResult<&[u8], Command<'_>> {
map!(i, take_till!(|_| { false }), { |_: &[u8]| Command::UnknownVCommand })
}
fn parse_vfile_op(i: &[u8]) -> IResult<&[u8], Command<'_>> {
alt_complete!(
i,
parse_vfile_open
| parse_vfile_close
| parse_vfile_pread
| parse_vfile_pwrite
| parse_vfile_fstat
| parse_vfile_unlink
| parse_vfile_readlink
| parse_vfile_setfs
| parse_unknown_vfile_op
)
}
fn v_command(i: &[u8]) -> IResult<&[u8], Command<'_>> {
alt_complete!(i,
tag!("vCtrlC") => { |_| Command::CtrlC }
| preceded!(tag!("vCont"),
alt_complete!(tag!("?") => { |_| Command::VContSupported }
| many0!(do_parse!(
tag!(";") >>
action: alt_complete!(tag!("c") => { |_| VCont::Continue }
| preceded!(tag!("C"), hex_byte) => { |sig| VCont::ContinueWithSignal(sig) }
| tag!("s") => { |_| VCont::Step }
| preceded!(tag!("S"), hex_byte) => { |sig| VCont::StepWithSignal(sig) }
| tag!("t") => { |_| VCont::Stop }
| do_parse!(tag!("r") >>
start: hex_value >>
tag!(",") >>
end: hex_value >>
(start, end)) => { |(start, end)| VCont::RangeStep(start..end) }
) >>
thread: opt!(complete!(preceded!(tag!(":"), parse_thread_id))) >>
(action, thread)
)) => { |actions| Command::VCont(actions) }
)) => {
|c| c
}
| preceded!(tag!("vKill;"), hex_value) => {
|pid| Command::Kill(Some(pid))
}
| preceded!(tag!("vFile:"), parse_vfile_op) => {
|c| c
}
| preceded!(tag!("v"), take_till!(|_| { false })) => {
|_| Command::UnknownVCommand
})
}
named!(parse_h_packet<&[u8], (SetThreadFor, ThreadId)>,
alt_complete!(
preceded!(tag!("Hg"), parse_thread_id) => {
|id| (SetThreadFor::ReadRegister, id)
}
| preceded!(tag!("Hc"), parse_thread_id) => {
|id| (SetThreadFor::Continue, id)
}
));
named!(parse_d_packet<&[u8], Option<u64>>,
alt_complete!(preceded!(tag!("D;"), hex_value) => {
|pid| Some(pid)
}
| tag!("D") => { |_| None }));
#[derive(Copy, Clone)]
enum ZAction {
Insert,
Remove,
}
named!(parse_z_action<&[u8], ZAction>,
alt_complete!(tag!("z") => { |_| ZAction::Remove } |
tag!("Z") => { |_| ZAction::Insert }));
#[derive(Copy, Clone)]
enum ZType {
SoftwareBreakpoint,
HardwareBreakpoint,
WriteWatchpoint,
ReadWatchpoint,
AccessWatchpoint,
}
named!(parse_z_type<&[u8], ZType>,
alt_complete!(tag!("0") => { |_| ZType::SoftwareBreakpoint } |
tag!("1") => { |_| ZType::HardwareBreakpoint } |
tag!("2") => { |_| ZType::WriteWatchpoint } |
tag!("3") => { |_| ZType::ReadWatchpoint } |
tag!("4") => { |_| ZType::AccessWatchpoint }));
named!(parse_cond_or_command_expression<&[u8], Bytecode>,
do_parse!(tag!("X") >>
len: hex_value >>
tag!(",") >>
expr: take!(len) >>
(Bytecode { bytecode: expr.to_vec() })));
named!(parse_condition_list<&[u8], Vec<Bytecode>>,
do_parse!(tag!(";") >>
list: many1!(parse_cond_or_command_expression) >>
(list)));
fn maybe_condition_list(i: &[u8]) -> IResult<&[u8], Option<Vec<Bytecode>>> {
match parse_condition_list(i) {
Done(rest, v) => Done(rest, Some(v)),
Incomplete(_i) => Done(i, None),
Error(_) => Done(i, None),
}
}
named!(parse_command_list<&[u8], Vec<Bytecode>>,
do_parse!(tag!(";cmds") >>
list: alt_complete!(do_parse!(persist_flag: hex_value >>
tag!(",") >>
cmd_list: many1!(parse_cond_or_command_expression) >>
(cmd_list)) |
many1!(parse_cond_or_command_expression)) >>
(list)));
fn maybe_command_list(i: &[u8]) -> IResult<&[u8], Option<Vec<Bytecode>>> {
match parse_command_list(i) {
Done(rest, v) => Done(rest, Some(v)),
Incomplete(_i) => Done(i, None),
Error(e) => Error(e),
}
}
named!(parse_cond_and_command_list<&[u8], (Option<Vec<Bytecode>>,
Option<Vec<Bytecode>>)>,
do_parse!(cond_list: maybe_condition_list >>
cmd_list: maybe_command_list >>
(cond_list, cmd_list)));
fn parse_z_packet(i: &[u8]) -> IResult<&[u8], Command<'_>> {
let (rest, (action, type_, addr, kind)) = try_parse!(
i,
do_parse!(
action: parse_z_action
>> type_: parse_z_type
>> tag!(",")
>> addr: hex_value
>> tag!(",")
>> kind: hex_value
>> (action, type_, addr, kind)
)
);
return match action {
ZAction::Insert => insert_command(rest, type_, addr, kind),
ZAction::Remove => Done(rest, remove_command(type_, addr, kind)),
};
fn insert_command(rest: &[u8], type_: ZType, addr: u64, kind: u64) -> IResult<&[u8], Command<'_>> {
match type_ {
ZType::SoftwareBreakpoint | ZType::HardwareBreakpoint => {
let (rest, (cond_list, cmd_list)) = parse_cond_and_command_list(rest).unwrap();
let c = (match type_ {
ZType::SoftwareBreakpoint => Command::InsertSoftwareBreakpoint,
ZType::HardwareBreakpoint => Command::InsertHardwareBreakpoint,
_ => panic!("cannot get here"),
})(Breakpoint::new(addr, kind, cond_list, cmd_list));
Done(rest, c)
}
ZType::WriteWatchpoint => Done(rest, Command::InsertWriteWatchpoint(Watchpoint::new(addr, kind))),
ZType::ReadWatchpoint => Done(rest, Command::InsertReadWatchpoint(Watchpoint::new(addr, kind))),
ZType::AccessWatchpoint => Done(rest, Command::InsertAccessWatchpoint(Watchpoint::new(addr, kind))),
}
}
fn remove_command<'a>(type_: ZType, addr: u64, kind: u64) -> Command<'a> {
match type_ {
ZType::SoftwareBreakpoint => Command::RemoveSoftwareBreakpoint(Breakpoint::new(addr, kind, None, None)),
ZType::HardwareBreakpoint => Command::RemoveHardwareBreakpoint(Breakpoint::new(addr, kind, None, None)),
ZType::WriteWatchpoint => Command::RemoveWriteWatchpoint(Watchpoint::new(addr, kind)),
ZType::ReadWatchpoint => Command::RemoveReadWatchpoint(Watchpoint::new(addr, kind)),
ZType::AccessWatchpoint => Command::RemoveAccessWatchpoint(Watchpoint::new(addr, kind)),
}
}
}
#[cfg(feature = "all_stop")]
fn all_stop_command(i: &[u8]) -> IResult<&[u8], Command<'_>> {
alt!(i,
tag!("c") => { |_| Command::Continue }
| tag!("s") => { |_| Command::Step }
)
}
#[cfg(not(feature = "all_stop"))]
fn all_stop_command(_i: &[u8]) -> IResult<&[u8], Command<'_>> {
IResult::Error(error_position!(ErrorKind::Alt, _i))
}
fn command(i: &[u8]) -> IResult<&[u8], Command<'_>> {
alt!(i,
tag!("!") => { |_| Command::EnableExtendedMode }
| tag!("?") => { |_| Command::TargetHaltReason }
| parse_d_packet => { |pid| Command::Detach(pid) }
| tag!("g") => { |_| Command::ReadGeneralRegisters }
| write_general_registers => { |bytes| Command::WriteGeneralRegisters(bytes) }
| parse_h_packet => { |(f, thread_id)| Command::SetCurrentThread(f, thread_id) }
| tag!("k") => { |_| Command::Kill(None) }
| read_memory => { |(addr, length)| Command::ReadMemory(MemoryRegion::new(addr, length)) }
| write_memory => { |(addr, length, bytes)| Command::WriteMemory(MemoryRegion::new(addr, length), bytes) }
| read_register => { |regno| Command::ReadRegister(regno) }
| write_register => { |(regno, bytes)| Command::WriteRegister(regno, bytes) }
| query => { |q| Command::Query(q) }
| tag!("r") => { |_| Command::Reset }
| preceded!(tag!("R"), take!(2)) => { |_| Command::Reset }
| parse_ping_thread => { |thread_id| Command::PingThread(thread_id) }
| v_command => { |command| command }
| write_memory_binary => { |(addr, length, bytes)| Command::WriteMemory(MemoryRegion::new(addr, length), bytes) }
| parse_z_packet => { |command| command }
| all_stop_command => { |command| command }
)
}
pub enum Error {
Error(u8),
Unimplemented,
}
#[derive(Clone, Copy, Debug)]
pub enum ProcessType {
Attached,
Created,
}
#[derive(Clone, Copy, Debug)]
pub enum StopReason {
Signal(u8),
Exited(u64, u8),
ExitedWithSignal(u64, u8),
ThreadExited(ThreadId, u64),
NoMoreThreads,
}
#[derive(Clone, Debug)]
pub enum SymbolLookupResponse {
Ok,
Symbol(String),
}
pub trait Handler {
fn query_supported_features(&self) -> Vec<String> {
vec![]
}
fn attached(&self, _pid: Option<u64>) -> Result<ProcessType, Error>;
fn detach(&self, _pid: Option<u64>) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn kill(&self, _pid: Option<u64>) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn ping_thread(&self, _id: ThreadId) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn read_memory(&self, _region: MemoryRegion) -> Result<Vec<u8>, Error> {
Err(Error::Unimplemented)
}
fn write_memory(&self, _address: u64, _bytes: &[u8]) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn read_register(&self, _register: u64) -> Result<Vec<u8>, Error> {
Err(Error::Unimplemented)
}
fn write_register(&self, _register: u64, _contents: &[u8]) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn read_general_registers(&self) -> Result<Vec<u8>, Error> {
Err(Error::Unimplemented)
}
fn write_general_registers(&self, _contents: &[u8]) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn read_bytes(
&self,
_object: String,
_annex: String,
_offset: u64,
_length: u64,
) -> Result<(Vec<u8>, bool), Error> {
Err(Error::Unimplemented)
}
fn current_thread(&self) -> Result<Option<ThreadId>, Error> {
Ok(None)
}
fn set_current_thread(&self, _for: SetThreadFor, _id: ThreadId) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn search_memory(&self, _address: u64, _length: u64, _bytes: &[u8]) -> Result<Option<u64>, Error> {
Err(Error::Unimplemented)
}
fn halt_reason(&self) -> Result<StopReason, Error>;
fn invoke(&self, _: &[u8]) -> Result<String, Error> {
Err(Error::Unimplemented)
}
fn set_address_randomization(&self, _enable: bool) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn catch_syscalls(&self, _syscalls: Option<Vec<u64>>) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn set_pass_signals(&self, _signals: Vec<u64>) -> Result<(), Error> {
Ok(())
}
fn set_program_signals(&self, _signals: Vec<u64>) -> Result<(), Error> {
Ok(())
}
fn thread_info(&self, _thread: ThreadId) -> Result<String, Error> {
Err(Error::Unimplemented)
}
fn thread_list(&self, _reset: bool) -> Result<Vec<ThreadId>, Error> {
Err(Error::Unimplemented)
}
fn insert_software_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn insert_hardware_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn insert_write_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn insert_read_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn insert_access_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn remove_software_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn remove_hardware_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn remove_write_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn remove_read_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn remove_access_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> {
Err(Error::Unimplemented)
}
fn query_supported_vcont(&self) -> Result<Cow<'static, [VContFeature]>, Error> {
Err(Error::Unimplemented)
}
fn vcont(&self, _request: Vec<(VCont, Option<ThreadId>)>) -> Result<StopReason, Error> {
Err(Error::Unimplemented)
}
fn fs(&self) -> Result<&dyn FileSystem, ()> {
Err(())
}
#[cfg(feature = "all_stop")]
fn process_continue(&self) -> Result<StopReason, Error> {
Err(Error::Unimplemented)
}
#[cfg(feature = "all_stop")]
fn process_step(&self) -> Result<StopReason, Error> {
Err(Error::Unimplemented)
}
fn process_symbol(&self, _sym_value: &str, _sym_name: &str) -> Result<SymbolLookupResponse, Error> {
Err(Error::Unimplemented)
}
#[cfg(feature = "lldb")]
fn register_info(&self, _reg: u64) -> Result<String, Error> {
Err(Error::Unimplemented)
}
#[cfg(feature = "lldb")]
fn process_info(&self) -> Result<String, Error> {
Err(Error::Unimplemented)
}
}
fn compute_checksum_incremental(bytes: &[u8], init: u8) -> u8 {
bytes.iter().fold(init, |sum, &b| sum.wrapping_add(b))
}
#[derive(Debug)]
enum HostIOResult {
Ok, Error(HostErrno), Integer(u64), Data(Vec<u8>), Stat(HostStat), }
#[derive(Debug)]
enum Response<'a> {
Empty,
Ok,
Error(u8),
String(Cow<'a, str>),
Output(String),
Bytes(Vec<u8>),
BytesOrEof(Vec<u8>, bool),
CurrentThread(Option<ThreadId>),
ProcessType(ProcessType),
Stopped(StopReason),
SearchResult(Option<u64>),
VContFeatures(Cow<'static, [VContFeature]>),
ThreadList(Vec<ThreadId>),
HostIOResult(HostIOResult),
SymbolName(String),
}
impl<'a, T> From<Result<T, Error>> for Response<'a>
where
Response<'a>: From<T>,
{
fn from(result: Result<T, Error>) -> Self {
match result {
Result::Ok(val) => val.into(),
Result::Err(Error::Error(val)) => Response::Error(val),
Result::Err(Error::Unimplemented) => Response::Empty,
}
}
}
impl<'a, T> From<Result<T, HostErrno>> for HostIOResult
where
HostIOResult: From<T>,
{
fn from(result: Result<T, HostErrno>) -> Self {
match result {
Result::Ok(val) => val.into(),
Result::Err(errno) => HostIOResult::Error(errno),
}
}
}
impl From<()> for HostIOResult {
fn from(_: ()) -> Self {
HostIOResult::Ok
}
}
impl From<u64> for HostIOResult {
fn from(i: u64) -> Self {
HostIOResult::Integer(i)
}
}
impl From<Vec<u8>> for HostIOResult {
fn from(data: Vec<u8>) -> Self {
HostIOResult::Data(data)
}
}
impl From<HostStat> for HostIOResult {
fn from(stat: HostStat) -> Self {
HostIOResult::Stat(stat)
}
}
impl<'a, T> From<IOResult<T>> for Response<'a>
where
HostIOResult: From<T>,
{
fn from(result: IOResult<T>) -> Self {
match result {
Result::Ok(val) => Response::HostIOResult(val.into()),
Result::Err(_) => Response::Empty,
}
}
}
impl<'a> From<()> for Response<'a> {
fn from(_: ()) -> Self {
Response::Ok
}
}
impl<'a> From<Vec<u8>> for Response<'a> {
fn from(response: Vec<u8>) -> Self {
Response::Bytes(response)
}
}
impl<'a> From<(Vec<u8>, bool)> for Response<'a> {
fn from((data, eof): (Vec<u8>, bool)) -> Self {
Response::BytesOrEof(data, eof)
}
}
impl<'a> From<Option<ThreadId>> for Response<'a> {
fn from(response: Option<ThreadId>) -> Self {
Response::CurrentThread(response)
}
}
impl<'a> From<Option<u64>> for Response<'a> {
fn from(response: Option<u64>) -> Self {
Response::SearchResult(response)
}
}
impl<'a> From<ProcessType> for Response<'a> {
fn from(process_type: ProcessType) -> Self {
Response::ProcessType(process_type)
}
}
impl<'a> From<StopReason> for Response<'a> {
fn from(reason: StopReason) -> Self {
Response::Stopped(reason)
}
}
impl<'a> From<String> for Response<'a> {
fn from(reason: String) -> Self {
Response::String(Cow::Owned(reason) as Cow<'_, str>)
}
}
impl<'a> From<Cow<'static, [VContFeature]>> for Response<'a> {
fn from(features: Cow<'static, [VContFeature]>) -> Self {
Response::VContFeatures(features)
}
}
impl<'a> From<Vec<ThreadId>> for Response<'a> {
fn from(threads: Vec<ThreadId>) -> Self {
Response::ThreadList(threads)
}
}
impl<'a> From<SymbolLookupResponse> for Response<'a> {
fn from(response: SymbolLookupResponse) -> Self {
match response {
SymbolLookupResponse::Ok => Response::Ok,
SymbolLookupResponse::Symbol(n) => Response::SymbolName(n),
}
}
}
impl<'a> From<HostIOResult> for Response<'a> {
fn from(result: HostIOResult) -> Self {
Response::HostIOResult(result)
}
}
struct PacketWriter<'a, W>
where
W: Write,
{
writer: &'a mut W,
checksum: u8,
}
impl<'a, W> PacketWriter<'a, W>
where
W: Write,
{
fn new(writer: &'a mut W) -> PacketWriter<'a, W> {
PacketWriter { writer, checksum: 0 }
}
fn finish(&mut self) -> io::Result<()> {
write!(self.writer, "#{:02x}", self.checksum)?;
self.writer.flush()?;
self.checksum = 0;
Ok(())
}
}
impl<'a, W> Write for PacketWriter<'a, W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let count = self.writer.write(buf)?;
self.checksum = compute_checksum_incremental(&buf[0..count], self.checksum);
Ok(count)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
fn write_thread_id<W>(writer: &mut W, thread_id: ThreadId) -> io::Result<()>
where
W: Write,
{
write!(writer, "p")?;
match thread_id.pid {
Id::All => write!(writer, "-1"),
Id::Any => write!(writer, "0"),
Id::Id(num) => write!(writer, "{:x}", num),
}?;
write!(writer, ".")?;
match thread_id.tid {
Id::All => write!(writer, "-1"),
Id::Any => write!(writer, "0"),
Id::Id(num) => write!(writer, "{:x}", num),
}
}
impl From<HostStat> for Vec<u8> {
fn from(stat: HostStat) -> Self {
let mut v = vec![];
write_stat(&mut v, stat).unwrap();
v
}
}
fn write_hex_data<W>(writer: &mut W, data: &[u8]) -> io::Result<()>
where
W: Write,
{
for b in data {
write!(writer, "{:02x}", b)?;
}
Ok(())
}
fn write_binary_data<W>(writer: &mut W, data: &[u8]) -> io::Result<()>
where
W: Write,
{
let mut remaining = data;
loop {
let unescaped = min(
memchr::memchr3(b'#', b'$', b'}', remaining).unwrap_or(remaining.len()),
memchr::memchr(b'*', remaining).unwrap_or(remaining.len()),
);
writer.write_all(&remaining[..unescaped])?;
remaining = &remaining[unescaped..];
match remaining.first() {
Some(byte) => writer.write_all(&[b'}', byte ^ 0x20])?,
None => break,
}
remaining = &remaining[1..];
}
Ok(())
}
fn write_response<W>(response: Response<'_>, writer: &mut W) -> io::Result<()>
where
W: Write,
{
trace!("Response: {:?}", response);
write!(writer, "$")?;
let mut writer = PacketWriter::new(writer);
match response {
Response::Ok => {
write!(writer, "OK")?;
}
Response::Empty => {}
Response::Error(val) => {
write!(writer, "E{:02x}", val)?;
}
Response::String(s) => {
write!(writer, "{}", s)?;
}
Response::Output(s) => {
write!(writer, "O")?;
for byte in s.as_bytes() {
write!(writer, "{:02x}", byte)?;
}
}
Response::Bytes(bytes) => {
for byte in bytes {
write!(writer, "{:02x}", byte)?;
}
}
Response::BytesOrEof(bytes, eof) => {
write!(writer, "{}", if eof { "l" } else { "m" })?;
write_binary_data(&mut writer, &bytes)?;
}
Response::CurrentThread(tid) => {
match tid {
None => write!(writer, "OK")?,
Some(thread_id) => {
write!(writer, "QC")?;
write_thread_id(&mut writer, thread_id)?;
}
};
}
Response::ProcessType(process_type) => {
match process_type {
ProcessType::Attached => write!(writer, "1")?,
ProcessType::Created => write!(writer, "0")?,
};
}
Response::SearchResult(maybe_addr) => match maybe_addr {
Some(addr) => write!(writer, "1,{:x}", addr)?,
None => write!(writer, "0")?,
},
Response::Stopped(stop_reason) => {
match stop_reason {
StopReason::Signal(signo) => write!(writer, "S{:02x}", signo)?,
StopReason::Exited(pid, status) => {
write!(writer, "W{:02x};process:{:x}", status, pid)?;
}
StopReason::ExitedWithSignal(pid, status) => {
write!(writer, "X{:x};process:{:x}", status, pid)?;
}
StopReason::ThreadExited(thread_id, status) => {
write!(writer, "w{:x};", status)?;
write_thread_id(&mut writer, thread_id)?;
}
StopReason::NoMoreThreads => write!(writer, "N")?,
}
}
Response::VContFeatures(features) => {
write!(writer, "vCont")?;
for &feature in &*features {
write!(writer, ";{}", feature as u8 as char)?;
}
}
Response::ThreadList(threads) => {
if threads.is_empty() {
write!(writer, "l")?;
} else {
write!(writer, "m")?;
for (i, &id) in threads.iter().enumerate() {
if i != 0 {
write!(writer, ",")?;
}
write_thread_id(&mut writer, id)?;
}
}
}
Response::SymbolName(name) => {
write!(writer, "qSymbol:")?;
write_hex_data(&mut writer, name.as_bytes())?;
}
Response::HostIOResult(result) => match result {
HostIOResult::Ok => write!(writer, "F0")?,
HostIOResult::Error(errno) => {
write!(writer, "F-1,{:x}", errno as u32)?;
}
HostIOResult::Integer(i) => write!(writer, "F{:x}", i)?,
HostIOResult::Data(v) => {
write!(writer, "F{:x};", v.len())?;
write_binary_data(&mut writer, &v)?;
}
HostIOResult::Stat(stat) => {
let data = <HostStat as Into<Vec<_>>>::into(stat);
write!(writer, "F{:x};", data.len())?;
write_binary_data(&mut writer, &data[..])?
}
},
}
writer.finish()
}
fn handle_supported_features<'a, H>(handler: &H, _features: &[GDBFeatureSupported<'a>]) -> Response<'static>
where
H: Handler,
{
let mut features = vec![
format!("PacketSize={:x}", MAX_PACKET_SIZE),
"QStartNoAckMode+".to_string(),
"multiprocess+".to_string(),
"QDisableRandomization+".to_string(),
"QCatchSyscalls+".to_string(),
"QPassSignals+".to_string(),
"QProgramSignals+".to_string(),
];
let mut new_features = handler.query_supported_features();
features.append(&mut new_features);
Response::String(Cow::Owned(features.join(";")) as Cow<'_, str>)
}
fn handle_packet<H, W>(data: &[u8], handler: &H, writer: &mut W) -> io::Result<bool>
where
H: Handler,
W: Write,
{
debug!("Command: {}", String::from_utf8_lossy(data));
let mut no_ack_mode = false;
let response = if let Done(_, command) = command(data) {
match command {
Command::EnableExtendedMode => Response::Ok,
Command::TargetHaltReason => handler.halt_reason().into(),
Command::ReadGeneralRegisters => handler.read_general_registers().into(),
Command::WriteGeneralRegisters(bytes) => handler.write_general_registers(&bytes[..]).into(),
Command::Kill(None) => {
drop(handler.kill(None));
Response::Empty
}
Command::Kill(pid) => handler.kill(pid).into(),
Command::Reset => Response::Empty,
Command::ReadRegister(regno) => handler.read_register(regno).into(),
Command::WriteRegister(regno, bytes) => handler.write_register(regno, &bytes[..]).into(),
Command::ReadMemory(region) => handler.read_memory(region).into(),
Command::WriteMemory(region, bytes) => {
if region.length as usize != bytes.len() {
Response::Error(1)
} else {
handler.write_memory(region.address, &bytes[..]).into()
}
}
Command::SetCurrentThread(f, thread_id) => handler.set_current_thread(f, thread_id).into(),
Command::Detach(pid) => handler.detach(pid).into(),
#[cfg(feature = "all_stop")]
Command::Continue => handler.process_continue().into(),
#[cfg(feature = "all_stop")]
Command::Step => handler.process_step().into(),
Command::Query(Query::Attached(pid)) => handler.attached(pid).into(),
Command::Query(Query::CurrentThread) => handler.current_thread().into(),
Command::Query(Query::Invoke(cmd)) => match handler.invoke(&cmd[..]) {
Result::Ok(val) => {
if val.is_empty() {
Response::Ok
} else {
Response::Output(val)
}
}
Result::Err(Error::Error(val)) => Response::Error(val),
Result::Err(Error::Unimplemented) => Response::Empty,
},
Command::Query(Query::SearchMemory { address, length, bytes }) => {
handler.search_memory(address, length, &bytes[..]).into()
}
Command::Query(Query::SupportedFeatures(features)) => handle_supported_features(handler, &features),
Command::Query(Query::StartNoAckMode) => {
no_ack_mode = true;
Response::Ok
}
Command::Query(Query::AddressRandomization(randomize)) => {
handler.set_address_randomization(randomize).into()
}
Command::Query(Query::CatchSyscalls(calls)) => handler.catch_syscalls(calls).into(),
Command::Query(Query::PassSignals(signals)) => handler.set_pass_signals(signals).into(),
Command::Query(Query::ProgramSignals(signals)) => handler.set_program_signals(signals).into(),
Command::Query(Query::ThreadInfo(thread_info)) => handler.thread_info(thread_info).into(),
Command::Query(Query::ThreadList(reset)) => handler.thread_list(reset).into(),
Command::Query(Query::ReadBytes {
object,
annex,
offset,
length,
}) => handler.read_bytes(object, annex, offset, length).into(),
#[cfg(feature = "lldb")]
Command::Query(Query::RegisterInfo(reg)) => handler.register_info(reg).into(),
#[cfg(feature = "lldb")]
Command::Query(Query::ProcessInfo) => handler.process_info().into(),
Command::Query(Query::Symbol(sym_value, sym_name)) => handler.process_symbol(&sym_value, &sym_name).into(),
Command::PingThread(thread_id) => handler.ping_thread(thread_id).into(),
Command::CtrlC => Response::Empty,
Command::UnknownVCommand => Response::Empty,
Command::InsertSoftwareBreakpoint(bp) => handler.insert_software_breakpoint(bp).into(),
Command::InsertHardwareBreakpoint(bp) => handler.insert_hardware_breakpoint(bp).into(),
Command::InsertWriteWatchpoint(wp) => handler.insert_write_watchpoint(wp).into(),
Command::InsertReadWatchpoint(wp) => handler.insert_read_watchpoint(wp).into(),
Command::InsertAccessWatchpoint(wp) => handler.insert_access_watchpoint(wp).into(),
Command::RemoveSoftwareBreakpoint(bp) => handler.remove_software_breakpoint(bp).into(),
Command::RemoveHardwareBreakpoint(bp) => handler.remove_hardware_breakpoint(bp).into(),
Command::RemoveWriteWatchpoint(wp) => handler.remove_write_watchpoint(wp).into(),
Command::RemoveReadWatchpoint(wp) => handler.remove_read_watchpoint(wp).into(),
Command::RemoveAccessWatchpoint(wp) => handler.remove_access_watchpoint(wp).into(),
Command::VContSupported => handler.query_supported_vcont().into(),
Command::VCont(list) => handler.vcont(list).into(),
Command::HostOpen(filename, mode, flags) => handler
.fs()
.and_then(|fs| {
fs.host_open(
filename,
HostOpenFlags::from_bits_truncate(mode as u32),
HostMode::from_bits_truncate(flags as u32),
)
})
.into(),
Command::HostClose(fd) => handler.fs().and_then(|fs| fs.host_close(fd)).into(),
Command::HostPRead(fd, count, offset) => {
handler.fs().and_then(|fs| fs.host_pread(fd, count, offset)).into()
}
Command::HostPWrite(fd, offset, data) => {
handler.fs().and_then(|fs| fs.host_pwrite(fd, offset, data)).into()
}
Command::HostFStat(fd) => handler.fs().and_then(|fs| fs.host_fstat(fd)).into(),
Command::HostUnlink(filename) => handler.fs().and_then(|fs| fs.host_unlink(filename)).into(),
Command::HostReadlink(filename) => handler.fs().and_then(|fs| fs.host_readlink(filename)).into(),
Command::HostSetFS(pid) => handler.fs().and_then(|fs| fs.host_setfs(pid)).into(),
}
} else {
Response::Empty
};
write_response(response, writer)?;
Ok(no_ack_mode)
}
fn offset(from: &[u8], to: &[u8]) -> usize {
let fst = from.as_ptr();
let snd = to.as_ptr();
snd as usize - fst as usize
}
fn run_parser(buf: &[u8]) -> Option<(usize, Packet)> {
if let Done(rest, packet) = packet_or_response(buf) {
Some((offset(buf, rest), packet))
} else {
None
}
}
pub fn process_packets_from<R, W, H>(mut reader: R, mut writer: W, handler: H)
where
R: Read,
W: Write,
H: Handler,
{
let mut buf = vec![0; MAX_PACKET_SIZE];
let mut endbuf = 0;
let mut ack_mode = true;
loop {
match reader.read(&mut buf[endbuf..]) {
Ok(n) if n > 0 => endbuf += n,
_ => break,
}
let mut parsed = 0;
while let Some((len, packet)) = run_parser(&buf[parsed..endbuf]) {
if let Packet::Data(ref data, ref _checksum) = packet {
if ack_mode && writer.write_all(&b"+"[..]).is_err() {
return;
}
let no_ack_mode = handle_packet(&data, &handler, &mut writer).unwrap_or(false);
if no_ack_mode {
ack_mode = false;
}
}
parsed += len;
}
buf.copy_within(parsed..endbuf, 0);
endbuf -= parsed;
}
}
#[test]
fn test_compute_checksum() {
assert_eq!(compute_checksum_incremental(&b""[..], 0), 0);
assert_eq!(
compute_checksum_incremental(&b"qSupported:multiprocess+;xmlRegisters=i386;qRelocInsn+"[..], 0),
0xb5
);
}
#[test]
fn test_checksum() {
assert_eq!(checksum(&b"00"[..]), Done(&b""[..], 0));
assert_eq!(checksum(&b"a1"[..]), Done(&b""[..], 0xa1));
assert_eq!(checksum(&b"1d"[..]), Done(&b""[..], 0x1d));
assert_eq!(checksum(&b"ff"[..]), Done(&b""[..], 0xff));
}
#[test]
fn test_packet() {
use nom::Needed;
assert_eq!(packet(&b"$#00"[..]), Done(&b""[..], (b""[..].to_vec(), 0)));
assert_eq!(packet(&b"$xyz#00"[..]), Done(&b""[..], (b"xyz"[..].to_vec(), 0)));
assert_eq!(packet(&b"$a#a1"[..]), Done(&b""[..], (b"a"[..].to_vec(), 0xa1)));
assert_eq!(
packet(&b"$foo#ffxyz"[..]),
Done(&b"xyz"[..], (b"foo"[..].to_vec(), 0xff))
);
assert_eq!(
packet(&b"$qSupported:multiprocess+;xmlRegisters=i386;qRelocInsn+#b5"[..]),
Done(
&b""[..],
(
b"qSupported:multiprocess+;xmlRegisters=i386;qRelocInsn+"[..].to_vec(),
0xb5
)
)
);
assert_eq!(packet(&b"$"[..]), Incomplete(Needed::Size(2)));
assert_eq!(packet(&b"$#"[..]), Incomplete(Needed::Size(4)));
assert_eq!(packet(&b"$xyz"[..]), Incomplete(Needed::Size(5)));
assert_eq!(packet(&b"$xyz#"[..]), Incomplete(Needed::Size(7)));
assert_eq!(packet(&b"$xyz#a"[..]), Incomplete(Needed::Size(7)));
}
#[test]
fn test_packet_or_response() {
assert_eq!(
packet_or_response(&b"$#00"[..]),
Done(&b""[..], Packet::Data(b""[..].to_vec(), 0))
);
assert_eq!(packet_or_response(&b"+"[..]), Done(&b""[..], Packet::Ack));
assert_eq!(packet_or_response(&b"-"[..]), Done(&b""[..], Packet::Nack));
}
#[test]
fn test_gdbfeaturesupported() {
assert_eq!(
gdbfeaturesupported(&b"multiprocess+"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::Yes(GDBFeature::multiprocess), FeatureSupported::Yes)
)
);
assert_eq!(
gdbfeaturesupported(&b"xmlRegisters=i386"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::Yes(GDBFeature::xmlRegisters), FeatureSupported::Value("i386"))
)
);
assert_eq!(
gdbfeaturesupported(&b"qRelocInsn-"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::Yes(GDBFeature::qRelocInsn), FeatureSupported::No)
)
);
assert_eq!(
gdbfeaturesupported(&b"vfork-events+"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::Yes)
)
);
assert_eq!(
gdbfeaturesupported(&b"vfork-events-"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::No)
)
);
assert_eq!(
gdbfeaturesupported(&b"unknown-feature+"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::No("unknown-feature"), FeatureSupported::Yes)
)
);
assert_eq!(
gdbfeaturesupported(&b"unknown-feature-"[..]),
Done(
&b""[..],
GDBFeatureSupported(Known::No("unknown-feature"), FeatureSupported::No)
)
);
}
#[test]
fn test_gdbfeature() {
assert_eq!(
gdbfeature(&b"multiprocess"[..]),
Done(&b""[..], Known::Yes(GDBFeature::multiprocess))
);
assert_eq!(
gdbfeature(&b"fork-events"[..]),
Done(&b""[..], Known::Yes(GDBFeature::fork_events))
);
assert_eq!(
gdbfeature(&b"some-unknown-feature"[..]),
Done(&b""[..], Known::No("some-unknown-feature"))
);
}
#[test]
fn test_query() {
let b = concat!(
"qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;",
"vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;",
"xmlRegisters=i386"
);
assert_eq!(
query(b.as_bytes()),
Done(
&b""[..],
Query::SupportedFeatures(vec![
GDBFeatureSupported(Known::Yes(GDBFeature::multiprocess), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::swbreak), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::hwbreak), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::qRelocInsn), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::fork_events), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::exec_events), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::vContSupported), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::QThreadEvents), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::no_resumed), FeatureSupported::Yes),
GDBFeatureSupported(Known::Yes(GDBFeature::xmlRegisters), FeatureSupported::Value("i386")),
])
)
);
}
#[test]
fn test_hex_value() {
assert_eq!(hex_value(&b""[..]), Incomplete(Needed::Size(1)));
assert_eq!(hex_value(&b","[..]), Error(nom::ErrorKind::TakeWhile1));
assert_eq!(hex_value(&b"a"[..]), Done(&b""[..], 0xa));
assert_eq!(hex_value(&b"10,"[..]), Done(&b","[..], 0x10));
assert_eq!(hex_value(&b"ff"[..]), Done(&b""[..], 0xff));
}
#[test]
fn test_parse_thread_id_element() {
assert_eq!(parse_thread_id_element(&b"0"[..]), Done(&b""[..], Id::Any));
assert_eq!(parse_thread_id_element(&b"-1"[..]), Done(&b""[..], Id::All));
assert_eq!(parse_thread_id_element(&b"23"[..]), Done(&b""[..], Id::Id(0x23)));
}
#[test]
fn test_parse_thread_id() {
assert_eq!(
parse_thread_id(&b"0"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::Any,
tid: Id::Any
}
)
);
assert_eq!(
parse_thread_id(&b"-1"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::All,
tid: Id::Any
}
)
);
assert_eq!(
parse_thread_id(&b"23"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::Id(0x23),
tid: Id::Any
}
)
);
assert_eq!(
parse_thread_id(&b"p23"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::Id(0x23),
tid: Id::All
}
)
);
assert_eq!(
parse_thread_id(&b"p0.0"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::Any,
tid: Id::Any
}
)
);
assert_eq!(
parse_thread_id(&b"p-1.23"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::All,
tid: Id::Id(0x23)
}
)
);
assert_eq!(
parse_thread_id(&b"pff.23"[..]),
Done(
&b""[..],
ThreadId {
pid: Id::Id(0xff),
tid: Id::Id(0x23)
}
)
);
}
#[test]
fn test_parse_v_commands() {
assert_eq!(v_command(&b"vKill;33"[..]), Done(&b""[..], Command::Kill(Some(0x33))));
assert_eq!(v_command(&b"vCtrlC"[..]), Done(&b""[..], Command::CtrlC));
assert_eq!(
v_command(&b"vMustReplyEmpty"[..]),
Done(&b""[..], Command::UnknownVCommand)
);
assert_eq!(v_command(&b"vCont?"[..]), Done(&b""[..], Command::VContSupported));
assert_eq!(v_command(&b"vCont"[..]), Done(&b""[..], Command::VCont(Vec::new())));
assert_eq!(
v_command(&b"vCont;c"[..]),
Done(&b""[..], Command::VCont(vec![(VCont::Continue, None)]))
);
assert_eq!(
v_command(&b"vCont;r1,2:p34.56;SAD:-1;c"[..]),
Done(
&b""[..],
Command::VCont(vec![
(
VCont::RangeStep(1..2),
Some(ThreadId {
pid: Id::Id(0x34),
tid: Id::Id(0x56)
})
),
(
VCont::StepWithSignal(0xAD),
Some(ThreadId {
pid: Id::All,
tid: Id::Any
})
),
(VCont::Continue, None)
])
)
);
assert_eq!(
v_command(&b"vFile:open:2f766d6c696e757a,0,0"[..]),
Done(
&b""[..],
Command::HostOpen(vec!(47, 118, 109, 108, 105, 110, 117, 122), 0, 0)
)
);
assert_eq!(
v_command(&b"vFile:close:85"[..]),
Done(&b""[..], Command::HostClose(0x85))
);
assert_eq!(
v_command(&b"vFile:pread:5,96,327"[..]),
Done(&b""[..], Command::HostPRead(5, 0x96, 0x327))
);
assert_eq!(
v_command(&b"vFile:pwrite:6,83,\x00"[..]),
Done(&b""[..], Command::HostPWrite(6, 0x83, vec![0]))
);
assert_eq!(
v_command(&b"vFile:pwrite:6,83,\x7d\x5d"[..]),
Done(&b""[..], Command::HostPWrite(6, 0x83, vec![0x7d]))
);
assert_eq!(
v_command(&b"vFile:fstat:32"[..]),
Done(&b""[..], Command::HostFStat(0x32))
);
assert_eq!(
v_command(&b"vFile:unlink:2f766d6c696e757a"[..]),
Done(
&b""[..],
Command::HostUnlink(vec!(47, 118, 109, 108, 105, 110, 117, 122))
)
);
assert_eq!(
v_command(&b"vFile:readlink:2f766d6c696e757a"[..]),
Done(
&b""[..],
Command::HostReadlink(vec!(47, 118, 109, 108, 105, 110, 117, 122))
)
);
assert_eq!(v_command(&b"vFile:setfs:0"[..]), Done(&b""[..], Command::HostSetFS(0)));
assert_eq!(
v_command(&b"vFile:fdopen:0"[..]),
Done(&b""[..], Command::UnknownVCommand)
);
}
#[test]
fn test_parse_d_packets() {
assert_eq!(parse_d_packet(&b"D"[..]), Done(&b""[..], None));
assert_eq!(parse_d_packet(&b"D;f0"[..]), Done(&b""[..], Some(240)));
}
#[test]
fn test_parse_write_memory() {
assert_eq!(
write_memory(&b"Mf0,3:ff0102"[..]),
Done(&b""[..], (240, 3, vec!(255, 1, 2)))
);
}
#[test]
fn test_parse_write_memory_binary() {
assert_eq!(
write_memory_binary(&b"Xf0,1: "[..]),
Done(&b""[..], (240, 1, vec!(0x20)))
);
assert_eq!(
write_memory_binary(&b"X90,10:}\x5d"[..]),
Done(&b""[..], (144, 16, vec!(0x7d)))
);
assert_eq!(
write_memory_binary(&b"X5,100:}\x5d}\x03"[..]),
Done(&b""[..], (5, 256, vec!(0x7d, 0x23)))
);
assert_eq!(
write_memory_binary(&b"Xff,2:}\x04\x9a"[..]),
Done(&b""[..], (255, 2, vec!(0x24, 0x9a)))
);
assert_eq!(
write_memory_binary(&b"Xff,2:\xce}\x0a\x9a"[..]),
Done(&b""[..], (255, 2, vec!(0xce, 0x2a, 0x9a)))
);
}
#[test]
fn test_parse_qrcmd() {
assert_eq!(
query(&b"qRcmd,736f6d657468696e67"[..]),
Done(&b""[..], Query::Invoke(b"something".to_vec()))
);
}
#[test]
fn test_parse_randomization() {
assert_eq!(
query(&b"QDisableRandomization:0"[..]),
Done(&b""[..], Query::AddressRandomization(true))
);
assert_eq!(
query(&b"QDisableRandomization:1"[..]),
Done(&b""[..], Query::AddressRandomization(false))
);
}
#[test]
fn test_parse_syscalls() {
assert_eq!(
query(&b"QCatchSyscalls:0"[..]),
Done(&b""[..], Query::CatchSyscalls(None))
);
assert_eq!(
query(&b"QCatchSyscalls:1"[..]),
Done(&b""[..], Query::CatchSyscalls(Some(vec!())))
);
assert_eq!(
query(&b"QCatchSyscalls:1;0;1;ff"[..]),
Done(&b""[..], Query::CatchSyscalls(Some(vec!(0, 1, 255))))
);
}
#[test]
fn test_parse_signals() {
assert_eq!(query(&b"QPassSignals:"[..]), Done(&b""[..], Query::PassSignals(vec!())));
assert_eq!(
query(&b"QPassSignals:0"[..]),
Done(&b""[..], Query::PassSignals(vec!(0)))
);
assert_eq!(
query(&b"QPassSignals:1;2;ff"[..]),
Done(&b""[..], Query::PassSignals(vec!(1, 2, 255)))
);
assert_eq!(
query(&b"QProgramSignals:0"[..]),
Done(&b""[..], Query::ProgramSignals(vec!(0)))
);
assert_eq!(
query(&b"QProgramSignals:1;2;ff"[..]),
Done(&b""[..], Query::ProgramSignals(vec!(1, 2, 255)))
);
}
#[test]
fn test_thread_info() {
assert_eq!(
query(&b"qThreadExtraInfo,ffff"[..]),
Done(
&b""[..],
Query::ThreadInfo(ThreadId {
pid: Id::Id(65535),
tid: Id::Any
})
)
);
}
#[test]
fn test_thread_list() {
assert_eq!(query(&b"qfThreadInfo"[..]), Done(&b""[..], Query::ThreadList(true)));
assert_eq!(query(&b"qsThreadInfo"[..]), Done(&b""[..], Query::ThreadList(false)));
}
#[test]
fn test_parse_write_register() {
assert_eq!(write_register(&b"Pff=1020"[..]), Done(&b""[..], (255, vec!(16, 32))));
}
#[test]
fn test_parse_write_general_registers() {
assert_eq!(
write_general_registers(&b"G0001020304"[..]),
Done(&b""[..], vec!(0, 1, 2, 3, 4))
);
}
#[test]
fn test_write_response() {
fn write_one(input: Response<'_>) -> io::Result<String> {
let mut result = Vec::new();
write_response(input, &mut result)?;
Ok(String::from_utf8(result).unwrap())
}
assert_eq!(write_one(Response::Empty).unwrap(), "$#00");
assert_eq!(write_one(Response::Ok).unwrap(), "$OK#9a");
assert_eq!(write_one(Response::Error(1)).unwrap(), "$E01#a6");
assert_eq!(
write_one(Response::CurrentThread(Some(ThreadId {
pid: Id::Id(255),
tid: Id::Id(1)
})))
.unwrap(),
"$QCpff.1#2f"
);
assert_eq!(
write_one(Response::ThreadList(vec!(
ThreadId {
pid: Id::Id(123),
tid: Id::Id(123)
},
ThreadId {
pid: Id::Id(456),
tid: Id::Any
},
)))
.unwrap(),
"$mp7b.7b,p1c8.0#03"
);
assert_eq!(write_one(Response::ThreadList(vec!())).unwrap(), "$l#6c");
assert_eq!(
write_one(Response::BytesOrEof(
Vec::from(&b"{Hello * World} #yee $999.99"[..]),
false
))
.unwrap(),
"$m{Hello }\n World}] }\x03yee }\x04999.99#54"
);
assert_eq!(
write_one(Response::BytesOrEof(
Vec::from(&b"does not need to be escaped"[..]),
true
))
.unwrap(),
"$ldoes not need to be escaped#23"
);
}
#[cfg(test)]
macro_rules! bytecode {
($elem:expr; $n:expr) => (Bytecode { bytecode: vec![$elem; $n] });
($($x:expr),*) => (Bytecode { bytecode: vec!($($x),*) })
}
#[test]
fn test_breakpoints() {
assert_eq!(
parse_z_packet(&b"Z0,1ff,0"[..]),
Done(
&b""[..],
Command::InsertSoftwareBreakpoint(Breakpoint::new(0x1ff, 0, None, None))
)
);
assert_eq!(
parse_z_packet(&b"z0,1fff,0"[..]),
Done(
&b""[..],
Command::RemoveSoftwareBreakpoint(Breakpoint::new(0x1fff, 0, None, None))
)
);
assert_eq!(
parse_z_packet(&b"Z1,ae,0"[..]),
Done(
&b""[..],
Command::InsertHardwareBreakpoint(Breakpoint::new(0xae, 0, None, None))
)
);
assert_eq!(
parse_z_packet(&b"z1,aec,0"[..]),
Done(
&b""[..],
Command::RemoveHardwareBreakpoint(Breakpoint::new(0xaec, 0, None, None))
)
);
assert_eq!(
parse_z_packet(&b"Z2,4cc,2"[..]),
Done(&b""[..], Command::InsertWriteWatchpoint(Watchpoint::new(0x4cc, 2)))
);
assert_eq!(
parse_z_packet(&b"z2,4ccf,4"[..]),
Done(&b""[..], Command::RemoveWriteWatchpoint(Watchpoint::new(0x4ccf, 4)))
);
assert_eq!(
parse_z_packet(&b"Z3,7777,4"[..]),
Done(&b""[..], Command::InsertReadWatchpoint(Watchpoint::new(0x7777, 4)))
);
assert_eq!(
parse_z_packet(&b"z3,77778,8"[..]),
Done(&b""[..], Command::RemoveReadWatchpoint(Watchpoint::new(0x77778, 8)))
);
assert_eq!(
parse_z_packet(&b"Z4,7777,10"[..]),
Done(&b""[..], Command::InsertAccessWatchpoint(Watchpoint::new(0x7777, 16)))
);
assert_eq!(
parse_z_packet(&b"z4,77778,20"[..]),
Done(&b""[..], Command::RemoveAccessWatchpoint(Watchpoint::new(0x77778, 32)))
);
assert_eq!(
parse_z_packet(&b"Z0,1ff,2;X1,0"[..]),
Done(
&b""[..],
Command::InsertSoftwareBreakpoint(Breakpoint::new(0x1ff, 2, Some(vec!(bytecode!('0' as u8))), None))
)
);
assert_eq!(
parse_z_packet(&b"Z1,1ff,2;X1,0"[..]),
Done(
&b""[..],
Command::InsertHardwareBreakpoint(Breakpoint::new(0x1ff, 2, Some(vec!(bytecode!('0' as u8))), None))
)
);
assert_eq!(
parse_z_packet(&b"Z0,1ff,2;cmdsX1,z"[..]),
Done(
&b""[..],
Command::InsertSoftwareBreakpoint(Breakpoint::new(0x1ff, 2, None, Some(vec!(bytecode!('z' as u8)))))
)
);
assert_eq!(
parse_z_packet(&b"Z1,1ff,2;cmdsX1,z"[..]),
Done(
&b""[..],
Command::InsertHardwareBreakpoint(Breakpoint::new(0x1ff, 2, None, Some(vec!(bytecode!('z' as u8)))))
)
);
assert_eq!(
parse_z_packet(&b"Z0,1ff,2;X1,0;cmdsX1,a"[..]),
Done(
&b""[..],
Command::InsertSoftwareBreakpoint(Breakpoint::new(
0x1ff,
2,
Some(vec!(bytecode!('0' as u8))),
Some(vec!(bytecode!('a' as u8)))
))
)
);
assert_eq!(
parse_z_packet(&b"Z1,1ff,2;X1,0;cmdsX1,a"[..]),
Done(
&b""[..],
Command::InsertHardwareBreakpoint(Breakpoint::new(
0x1ff,
2,
Some(vec!(bytecode!('0' as u8))),
Some(vec!(bytecode!('a' as u8)))
))
)
);
}
#[test]
fn test_cond_or_command_list() {
assert_eq!(
parse_condition_list(&b";X1,a"[..]),
Done(&b""[..], vec!(bytecode!('a' as u8)))
);
assert_eq!(
parse_condition_list(&b";X2,ab"[..]),
Done(&b""[..], vec!(bytecode!('a' as u8, 'b' as u8)))
);
assert_eq!(
parse_condition_list(&b";X1,zX1,y"[..]),
Done(&b""[..], vec!(bytecode!('z' as u8), bytecode!('y' as u8)))
);
assert_eq!(
parse_condition_list(&b";X1,zX10,yyyyyyyyyyyyyyyy"[..]),
Done(&b""[..], vec!(bytecode!('z' as u8), bytecode!['y' as u8; 16]))
);
assert_eq!(
parse_command_list(&b";cmdsX1,a"[..]),
Done(&b""[..], vec!(bytecode!('a' as u8)))
);
assert_eq!(
parse_command_list(&b";cmdsX2,ab"[..]),
Done(&b""[..], vec!(bytecode!('a' as u8, 'b' as u8)))
);
assert_eq!(
parse_command_list(&b";cmdsX1,zX1,y"[..]),
Done(&b""[..], vec!(bytecode!('z' as u8), bytecode!('y' as u8)))
);
assert_eq!(
parse_command_list(&b";cmdsX1,zX10,yyyyyyyyyyyyyyyy"[..]),
Done(&b""[..], vec!(bytecode!('z' as u8), bytecode!['y' as u8; 16]))
);
}
#[test]
fn test_qxfer() {
assert_eq!(
query(&b"qXfer:features:read:target.xml:0,1000"[..]),
Done(
&b""[..],
Query::ReadBytes {
object: String::from("features"),
annex: String::from("target.xml"),
offset: 0,
length: 4096,
}
)
);
}
#[test]
fn test_write_binary_data() {
check(vec![1, 2, 3], vec![1, 2, 3]);
check(vec![0x7d, 2, 3], vec![0x7d, 0x5d, 2, 3]);
check(vec![0x23, 2, 3], vec![0x7d, 0x03, 2, 3]);
check(vec![0x24, 2, 3], vec![0x7d, 0x04, 2, 3]);
check(vec![0x2a, 2, 3], vec![0x7d, 0x0a, 2, 3]);
check(vec![8, 9, 0x7d], vec![8, 9, 0x7d, 0x5d]);
check(vec![8, 9, 0x23], vec![8, 9, 0x7d, 0x03]);
check(vec![8, 9, 0x24], vec![8, 9, 0x7d, 0x04]);
check(vec![8, 9, 0x2a], vec![8, 9, 0x7d, 0x0a]);
check(vec![8, 9, 0x7d, 5, 6], vec![8, 9, 0x7d, 0x5d, 5, 6]);
check(vec![8, 9, 0x23, 5, 6], vec![8, 9, 0x7d, 0x03, 5, 6]);
check(vec![8, 9, 0x24, 5, 6], vec![8, 9, 0x7d, 0x04, 5, 6]);
check(vec![8, 9, 0x2a, 5, 6], vec![8, 9, 0x7d, 0x0a, 5, 6]);
fn check(data: Vec<u8>, output: Vec<u8>) {
let mut v = vec![];
write_binary_data(&mut v, &data).unwrap();
assert_eq!(v, output);
}
}