use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as _;
use std::num::NonZeroU64;
use super::super::Core;
use super::memory::peek;
use super::packet::{Negotiated, from_hex, hex, to_hex};
use super::points::{Breakpoint, Watchpoint};
use super::registers::named;
use crate::emulator::{KernelThread, WalkThreads};
pub(super) const PROCESS: u64 = 1;
pub(super) fn ours(process: Option<&str>) -> bool {
process.and_then(hex) == Some(PROCESS)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct ThreadId(NonZeroU64);
pub(super) const CORE: ThreadId = ThreadId(NonZeroU64::MIN);
impl ThreadId {
fn render(self, negotiated: Negotiated) -> String {
if negotiated.multiprocess {
format!("p{PROCESS:x}.{:x}", self.0)
} else {
format!("{:x}", self.0)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Thread {
All,
Any,
One(ThreadId),
}
impl Thread {
pub(super) fn parse(text: &str) -> Option<Self> {
let Some(process) = text.strip_prefix('p') else {
return Self::one_of(text);
};
let (process, thread) = process.split_once('.').unwrap_or((process, "-1"));
let thread = Self::one_of(thread)?;
match (process, thread) {
("-1", Self::One(_)) => None,
("-1" | "0", _) => Some(thread),
_ => (hex(process)? == PROCESS).then_some(thread),
}
}
fn one_of(text: &str) -> Option<Self> {
match text {
"-1" => Some(Self::All),
"0" => Some(Self::Any),
_ => NonZeroU64::new(hex(text)?).map(|id| Self::One(ThreadId(id))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Picked<'a> {
Running,
Switched(&'a BTreeMap<&'static str, Vec<u8>>),
Gone,
}
#[derive(Debug)]
pub(super) struct Threads<'a> {
kernels: &'a [Box<dyn WalkThreads>],
found: BTreeMap<&'static str, u64>,
asking: usize,
picked: Thread,
walked: Walked,
only_core: bool,
}
#[derive(Debug)]
enum Walked {
Unasked,
Core,
Kernel {
current: NonZeroU64,
each: Vec<KernelThread>,
},
}
impl<'a> Threads<'a> {
pub(super) fn new(kernels: &'a [Box<dyn WalkThreads>]) -> Self {
Self {
kernels,
found: BTreeMap::new(),
asking: 0,
picked: Thread::Any,
walked: Walked::Unasked,
only_core: false,
}
}
pub(super) fn connected(&mut self) {
*self = Self::new(self.kernels);
}
pub(super) fn forget(&mut self) {
self.walked = Walked::Unasked;
}
pub(super) fn only_core(&mut self, only: bool) {
self.only_core = only;
self.forget();
}
fn walk(&mut self, core: &mut Core) -> Option<(NonZeroU64, &[KernelThread])> {
if self.only_core {
self.walked = Walked::Core;
}
if matches!(self.walked, Walked::Unasked) {
let found = &self.found;
let kernel = self.kernels.iter().find(|kernel| {
kernel
.symbols()
.iter()
.all(|symbol| !symbol.required || found.contains_key(symbol.name))
});
let walked = kernel.and_then(|kernel| {
let addresses: Vec<Option<u64>> = kernel
.symbols()
.iter()
.map(|symbol| found.get(symbol.name).copied())
.collect();
let registers = named(core.machine.cpu());
let endian = core.machine.memory.endian();
let each = kernel.walk(&addresses, ®isters, endian, &mut |address, width| {
peek(core, address, width)
})?;
let current = running(&each)?;
Some(Walked::Kernel { current, each })
});
self.walked = walked.unwrap_or(Walked::Core);
}
match &self.walked {
Walked::Kernel { current, each } => Some((*current, each)),
Walked::Unasked | Walked::Core => None,
}
}
pub(super) fn current(&mut self, core: &mut Core) -> ThreadId {
self.walk(core)
.map_or(CORE, |(current, _)| ThreadId(current))
}
pub(super) fn stopped(&mut self, core: &mut Core, reason: Reason) -> Stopped {
Stopped {
reason,
thread: self.current(core),
}
}
fn thread(&mut self, core: &mut Core, id: ThreadId) -> Option<&KernelThread> {
let (_, each) = self.walk(core)?;
each.iter().find(|thread| thread.id == id.0)
}
fn has(&mut self, core: &mut Core, thread: Thread) -> bool {
match thread {
Thread::All | Thread::Any => true,
Thread::One(id) => id == CORE || self.thread(core, id).is_some(),
}
}
pub(super) fn select(&mut self, core: &mut Core, rest: &str) -> &'static str {
let mut characters = rest.chars();
let operation = characters.next();
let Some(thread) =
Thread::parse(characters.as_str()).filter(|&thread| self.has(core, thread))
else {
return "E01";
};
match operation {
Some('g') => {
self.picked = thread;
"OK"
}
Some('c') => "OK",
_ => "E01",
}
}
pub(super) fn alive(&mut self, core: &mut Core, rest: &str) -> &'static str {
if Thread::parse(rest).is_some_and(|thread| self.has(core, thread)) {
"OK"
} else {
"E01"
}
}
pub(super) fn picked(&mut self, core: &mut Core) -> Picked<'_> {
let id = match self.picked {
Thread::One(id) if id != CORE => id,
_ => return Picked::Running,
};
match self.thread(core, id) {
None => Picked::Gone,
Some(thread) => thread
.saved
.as_ref()
.map_or(Picked::Running, Picked::Switched),
}
}
pub(super) fn query(
&mut self,
core: &mut Core,
rest: &str,
negotiated: Negotiated,
attached: bool,
) -> Option<String> {
match rest {
"C" => Some(format!("QC{}", self.current(core).render(negotiated))),
"fThreadInfo" if attached => {
let ids: Vec<String> = match self.walk(core) {
Some((_, each)) => each
.iter()
.map(|thread| ThreadId(thread.id).render(negotiated))
.collect(),
None => vec![CORE.render(negotiated)],
};
Some(format!("m{}", ids.join(",")))
}
"fThreadInfo" | "sThreadInfo" => Some("l".to_owned()),
_ => None,
}
}
pub(super) fn extra_info(&mut self, core: &mut Core, id: &str) -> String {
let Some(Thread::One(id)) = Thread::parse(id) else {
return "E01".to_owned();
};
let id = if id == CORE { self.current(core) } else { id };
match self.thread(core, id) {
Some(KernelThread {
name: Some(name),
state,
..
}) => to_hex(format!("{name}, {state}").as_bytes()),
Some(thread) => to_hex(thread.state.as_bytes()),
None if id == CORE => to_hex(b"core"),
None => "E01".to_owned(),
}
}
pub(super) fn symbol(&mut self, arguments: &str) -> String {
let kernels = self.kernels;
let wanted = || kernels.iter().flat_map(|kernel| kernel.symbols());
let (value, name) = arguments.split_once(':').unwrap_or((arguments, ""));
if name.is_empty() {
self.found.clear();
self.asking = 0;
} else {
let asked = wanted().nth(self.asking);
match asked.filter(|symbol| from_hex(name).as_deref() == Some(symbol.name.as_bytes())) {
Some(symbol) => {
if let Some(address) = hex(value) {
self.found.insert(symbol.name, address);
}
self.asking += 1;
}
None => self.asking = usize::MAX,
}
}
self.forget();
match wanted().nth(self.asking) {
Some(symbol) => format!("qSymbol:{}", to_hex(symbol.name.as_bytes())),
None => "OK".to_owned(),
}
}
}
fn running(each: &[KernelThread]) -> Option<NonZeroU64> {
let mut running = each.iter().filter(|thread| thread.saved.is_none());
let current = running.next()?.id;
let ids: BTreeSet<NonZeroU64> = each.iter().map(|thread| thread.id).collect();
let checks_out = running.next().is_none()
&& ids.len() == each.len()
&& (current == CORE.0 || !ids.contains(&CORE.0));
checks_out.then_some(current)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Stopped {
pub(super) reason: Reason,
pub(super) thread: ThreadId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Reason {
Interrupt,
Halt,
Begin,
Trap,
Breakpoint(Breakpoint),
Watch(Watchpoint, u64),
}
pub(super) fn stop_reply(stopped: Stopped, negotiated: Negotiated) -> String {
let signal = match stopped.reason {
Reason::Interrupt => 2,
Reason::Halt => 0,
_ => 5,
};
let mut reply = format!("T{signal:02x}thread:{};", stopped.thread.render(negotiated));
match stopped.reason {
Reason::Watch(watchpoint, address) => {
write!(reply, "{}:{address:x};", watchpoint.reason()).expect("a String takes it");
}
Reason::Breakpoint(Breakpoint::Software) if negotiated.swbreak => {
reply.push_str("swbreak:;");
}
Reason::Breakpoint(Breakpoint::Hardware) if negotiated.hwbreak => {
reply.push_str("hwbreak:;");
}
Reason::Begin => reply.push_str("replaylog:begin;"),
_ => {}
}
reply
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::KernelSymbol;
use crate::soc::{Endian, Width};
#[derive(Debug)]
struct Asks(&'static [KernelSymbol]);
const fn required(name: &'static str) -> KernelSymbol {
KernelSymbol {
name,
required: true,
}
}
const FIRST: &[KernelSymbol] = &[required("first"), required("second")];
const SECOND: &[KernelSymbol] = &[required("third"), required("fourth"), required("fifth")];
impl WalkThreads for Asks {
fn symbols(&self) -> &[KernelSymbol] {
self.0
}
fn walk(
&self,
_found: &[Option<u64>],
_registers: &BTreeMap<&str, Vec<u8>>,
_endian: Endian,
_read: &mut dyn FnMut(u64, Width) -> Option<u64>,
) -> Option<Vec<KernelThread>> {
None
}
}
#[test]
fn a_stop_names_the_thread_that_was_running() {
let stopped = Stopped {
reason: Reason::Interrupt,
thread: ThreadId(NonZeroU64::new(0x2000_1F00).unwrap()),
};
let multiprocess = Negotiated {
multiprocess: true,
..Negotiated::default()
};
assert_eq!(stop_reply(stopped, multiprocess), "T02thread:p1.20001f00;");
assert_eq!(
stop_reply(stopped, Negotiated::default()),
"T02thread:20001f00;"
);
}
#[test]
fn qsymbol_asks_for_each_kernels_symbols_then_answers_ok() {
let kernels: [Box<dyn WalkThreads>; 2] = [Box::new(Asks(FIRST)), Box::new(Asks(SECOND))];
let mut threads = Threads::new(&kernels);
let asks = |name: &str| format!("qSymbol:{}", to_hex(name.as_bytes()));
let wanted = ["first", "second", "third", "fourth", "fifth"];
assert_eq!(threads.symbol(":"), asks(wanted[0]));
for (at, name) in wanted.iter().enumerate() {
let answer = if at % 2 == 0 {
format!("{:x}:{}", 0x2000_0000 + at, to_hex(name.as_bytes()))
} else {
format!(":{}", to_hex(name.as_bytes()))
};
let next = wanted
.get(at + 1)
.map_or("OK".to_owned(), |next| asks(next));
assert_eq!(threads.symbol(&answer), next, "{name}");
}
assert_eq!(threads.found.len(), wanted.len().div_ceil(2));
assert_eq!(threads.found.get("first"), Some(&0x2000_0000));
assert_eq!(threads.symbol(":"), asks(wanted[0]));
assert!(threads.found.is_empty());
let stranger = format!("8:{}", to_hex(b"main"));
assert_eq!(threads.symbol(&stranger), "OK");
}
#[test]
fn a_thread_id_is_read_in_either_form() {
let core = Some(Thread::One(CORE));
for text in ["1", "p1.1", "p0.1"] {
assert_eq!(Thread::parse(text), core, "{text}");
}
for (text, thread) in [
("-1", Thread::All),
("0", Thread::Any),
("p1", Thread::All),
("p1.-1", Thread::All),
("p-1", Thread::All),
("p1.0", Thread::Any),
] {
assert_eq!(Thread::parse(text), Some(thread), "{text}");
}
for text in ["p2.1", "p-1.1", "", "p", "x", "p1.", "p1.x"] {
assert_eq!(Thread::parse(text), None, "{text}");
}
assert_eq!(
Thread::parse("2"),
Some(Thread::One(ThreadId(NonZeroU64::new(2).unwrap())))
);
}
}