use std::fmt::Write as _;
use std::ops::Range;
use super::super::Core;
use super::memory::{Reads, span};
use super::packet::PACKET_SIZE;
use super::registers::description;
use super::threads::ours;
use super::{Asked, Debugger};
use crate::emulator::Alias;
use crate::soc::{Region, RegionKind};
const FEATURES: &[&str] = &[
"qXfer:features:read+",
"vContSupported+",
"multiprocess+",
"swbreak+",
"hwbreak+",
"QStartNoAckMode+",
"qXfer:memory-map:read+",
"ConditionalBreakpoints+",
"BreakpointCommands+",
"QNonStop+",
];
impl Debugger<'_> {
pub(super) fn query(&mut self, core: &mut Core, rest: &str) -> Asked {
let reply = |text: &str| Asked::Reply(text.to_owned());
let (name, arguments) = rest.split_once([':', ',']).unwrap_or((rest, ""));
let negotiated = self.negotiated();
match name {
"Supported" => {
let offered = |feature| arguments.split(';').any(|each| each == feature);
if let Some(connection) = self.connection.as_mut() {
let negotiated = &mut connection.negotiated;
negotiated.multiprocess = offered("multiprocess+");
negotiated.swbreak = offered("swbreak+");
negotiated.hwbreak = offered("hwbreak+");
}
let reverse = if Self::reverses(core) {
";ReverseStep+;ReverseContinue+"
} else {
""
};
reply(&format!(
"PacketSize={PACKET_SIZE:x};{}{reverse}",
FEATURES.join(";")
))
}
"Xfer" => match arguments.strip_prefix("memory-map:read:") {
Some(annex) => reply(&piece(&self.map.xml, annex.strip_prefix(':'))),
None => reply(&features(arguments, &description(core.machine.cpu()))),
},
"Attached" if arguments.is_empty() || ours(Some(arguments)) => reply("1"),
"Attached" => reply("E01"),
"Symbol" => reply(&self.threads.symbol(arguments)),
"ThreadExtraInfo" => reply(&self.threads.extra_info(core, arguments)),
"Rcmd" => self.monitor(core, arguments),
"CRC" => reply(&checksum(core, arguments)),
_ => {
let attached = self.attached();
reply(
&self
.threads
.query(core, rest, negotiated, attached)
.unwrap_or_default(),
)
}
}
}
pub(super) fn general_set(&mut self, rest: &str) -> Asked {
match (rest, self.connection.as_mut()) {
("StartNoAckMode", Some(connection)) => {
connection.negotiated.no_ack = true;
Asked::Reply("OK".to_owned())
}
_ => Asked::Reply(String::new()),
}
}
}
fn checksum(core: &mut Core, arguments: &str) -> String {
let Some((address, length)) = span(arguments) else {
return "E01".to_owned();
};
let bytes = core.debugger_read(address, length, Reads::Peeks);
if bytes.len() as u64 == length {
format!("C{:08x}", crc(&bytes))
} else {
"E01".to_owned()
}
}
fn crc(bytes: &[u8]) -> u32 {
bytes.iter().fold(u32::MAX, |crc, &byte| {
(0..8).fold(crc ^ u32::from(byte) << 24, |crc, _| {
if crc & 1 << 31 == 0 {
crc << 1
} else {
crc << 1 ^ IEEE_802_3
}
})
})
}
const IEEE_802_3: u32 = 0x04C1_1DB7;
fn features(arguments: &str, target_xml: &str) -> String {
let Some(annex) = arguments.strip_prefix("features:read:") else {
return String::new();
};
piece(target_xml, annex.strip_prefix("target.xml:"))
}
fn piece(xml: &str, span: Option<&str>) -> String {
let Some((offset, length)) = span.and_then(super::memory::span) else {
return "E00".to_owned();
};
let xml = xml.as_bytes();
let start = usize::try_from(offset).unwrap_or(usize::MAX).min(xml.len());
let end = start
.saturating_add(usize::try_from(length).unwrap_or(usize::MAX))
.min(xml.len());
let more = if end < xml.len() { 'm' } else { 'l' };
let chunk: String = xml[start..end]
.iter()
.map(|&byte| char::from(byte))
.collect();
format!("{more}{chunk}")
}
#[derive(Debug, Default)]
pub(in super::super) struct Map {
xml: String,
flash: Vec<(u8, Vec<u64>)>,
}
impl Map {
pub(in super::super) fn new(
regions: &[Region],
aliases: &[Alias],
peripherals: &[Range<u64>],
own: &[Range<u64>],
) -> Self {
let mut map = Self::default();
let mut entries = Vec::new();
let mut taken = Vec::new();
for region in regions {
let bases = aliases
.iter()
.filter(|alias| alias.region == region.name)
.map(|alias| alias.at);
for base in std::iter::once(region.base).chain(bases) {
taken.push(base..base + region.size);
match region.kind {
RegionKind::Flash => map.flash(base, region, &mut entries),
RegionKind::Ram => entries.push((base, memory("ram", base, region.size, None))),
RegionKind::System => {
entries.push((base, memory("rom", base, region.size, None)));
}
}
}
}
for range in disjoint(peripherals.iter().chain(own).cloned().collect(), &taken) {
entries.push((
range.start,
memory("ram", range.start, range.end - range.start, None),
));
}
entries.sort_by_key(|&(start, _)| start);
map.xml.push_str(MAP_HEADER);
for (_, entry) in entries {
map.xml.push_str(&entry);
}
map.xml.push_str("</memory-map>\n");
map
}
fn flash(&mut self, base: u64, region: &Region, entries: &mut Vec<(u64, String)>) {
let runs: Vec<(u64, u64)> = if region.blocks.is_empty() {
vec![(region.size, 1)]
} else {
region
.blocks
.iter()
.map(|blocks| (blocks.size.get(), u64::from(blocks.count.get())))
.collect()
};
let mut starts = vec![base];
for (size, count) in runs {
let at = *starts.last().expect("the region's base");
entries.push((at, memory("flash", at, size * count, Some(size))));
starts.extend((1..=count).map(|block| at + block * size));
}
self.flash.push((region.erased, starts));
}
pub(super) fn erases(&self, address: u64, length: u64) -> Option<u8> {
self.flash
.iter()
.find(|(_, starts)| {
length > 0
&& starts.binary_search(&address).is_ok()
&& starts.binary_search(&(address + length)).is_ok()
})
.map(|&(erased, _)| erased)
}
pub(super) fn holds(&self, address: u64, length: u64) -> bool {
self.flash.iter().any(|(_, starts)| {
starts.first().is_some_and(|&first| first <= address)
&& starts.last().is_some_and(|&last| address + length <= last)
})
}
}
const MAP_HEADER: &str = "<?xml version=\"1.0\"?>\n<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB \
Memory Map V1.0//EN\" \"http://sourceware.org/gdb/gdb-memory-map.dtd\">\n\
<memory-map>\n";
fn memory(kind: &str, start: u64, length: u64, blocks: Option<u64>) -> String {
let mut entry = format!("<memory type=\"{kind}\" start=\"{start:#x}\" length=\"{length:#x}\"");
match blocks {
Some(size) => writeln!(
entry,
"><property name=\"blocksize\">{size:#x}</property></memory>"
),
None => writeln!(entry, "/>"),
}
.expect("a String takes it");
entry
}
fn disjoint(mut ranges: Vec<Range<u64>>, taken: &[Range<u64>]) -> Vec<Range<u64>> {
ranges.sort_by_key(|range| range.start);
let mut merged: Vec<Range<u64>> = Vec::new();
for range in ranges {
match merged.last_mut() {
Some(last) if range.start <= last.end => last.end = last.end.max(range.end),
_ => merged.push(range),
}
}
for cut in taken {
merged = merged
.into_iter()
.flat_map(|range| {
[
range.start..cut.start.min(range.end),
cut.end.max(range.start)..range.end,
]
})
.filter(|range| !range.is_empty())
.collect();
}
merged
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_target_description_comes_in_pieces_and_says_when_it_ends() {
const XML: &str =
r#"<?xml version="1.0"?><target><architecture>any</architecture></target>"#;
let first = features("features:read:target.xml:0,10", XML);
assert!(first.starts_with('m'), "{first}");
assert_eq!(first.len(), 0x11);
let whole = XML.len();
let last = features(
&format!("features:read:target.xml:{:x},1000", whole - 4),
XML,
);
let tail = last.strip_prefix('l').expect("the last piece");
assert_eq!(tail.len(), 4);
assert!(XML.ends_with(tail), "{last}");
}
#[test]
fn ranges_merge_where_they_meet_and_lose_what_the_regions_take() {
let ranges = vec![0x30..0x40, 0x10..0x20, 0x18..0x30, 0x50..0x60, 0x70..0x80];
assert_eq!(
disjoint(ranges, &[0x1C..0x24, 0x55..0x58, 0x70..0x90]),
[0x10..0x1C, 0x24..0x40, 0x50..0x55, 0x58..0x60]
);
}
#[test]
fn qcrc_matches_e4s_crc_of_the_same_bytes() {
assert_eq!(crc(&[]), 0xFFFF_FFFF);
assert_eq!(crc(&[0]), 0x4E08_BFB4);
assert_eq!(crc(b"123456789"), 0x0376_E6E7);
}
}