use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
use crate::elf::{self, File};
use crate::emulator::{Alias, CoreSpec};
use crate::soc::Region;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("reading {}: {source}", path.display())]
Read {
path: PathBuf,
source: io::Error,
},
#[error("{} {source}", path.display())]
Elf {
path: PathBuf,
source: elf::Error,
},
#[error("{} is an ELF file, which says where its bytes go, so give it no address", path.display())]
Addressed {
path: PathBuf,
},
#[error("{} isn't an ELF file, so it needs an address to go at", path.display())]
Unaddressed {
path: PathBuf,
},
#[error("{} places {start:#x} to {end:#x}, and no one memory region holds all of it", path.display())]
Outside {
path: PathBuf,
start: u64,
end: u64,
},
#[error("{first} and {second} overlap from {at:#x}")]
Overlap {
first: Span,
second: Span,
at: u64,
},
#[error("the core boots from {}, and no image places anything there", at_in_memory(*at, *held))]
Unbooted {
at: u64,
held: Option<u64>,
},
#[error("the core can't boot from {at:#x}, which isn't a multiple of {alignment:#x}")]
Misaligned {
at: u64,
alignment: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
pub path: PathBuf,
pub start: u64,
pub end: u64,
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} at {:#x} to {:#x}",
self.path.display(),
self.start,
self.end
)
}
}
fn at_in_memory(at: u64, held: Option<u64>) -> String {
match held {
Some(held) if held != at => format!("{at:#x}, which is {held:#x}"),
_ => format!("{at:#x}"),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Image {
path: PathBuf,
bytes: Vec<u8>,
segments: Vec<(u64, std::ops::Range<usize>)>,
at: Option<u64>,
}
impl Image {
pub fn read(path: &Path, at: Option<u64>) -> Result<Self, Error> {
let bytes = std::fs::read(path).map_err(|source| Error::Read {
path: path.to_owned(),
source,
})?;
Self::new(path, bytes, at)
}
pub fn new(path: &Path, bytes: Vec<u8>, at: Option<u64>) -> Result<Self, Error> {
let segments = match (File::new(&bytes), at) {
(Ok(file), None) => file
.loads()
.map_err(|source| Error::Elf {
path: path.to_owned(),
source,
})?
.into_iter()
.map(|load| {
let start = usize::try_from(load.offset).expect("inside the file");
let end = start + usize::try_from(load.length).expect("inside the file");
(load.address, start..end)
})
.collect(),
(Ok(_), Some(_)) => {
return Err(Error::Addressed {
path: path.to_owned(),
});
}
(Err(_), Some(at)) => vec![(at, 0..bytes.len())],
(Err(_), None) => {
return Err(Error::Unaddressed {
path: path.to_owned(),
});
}
};
Ok(Self {
path: path.to_owned(),
bytes,
segments,
at,
})
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
pub fn segments(&self) -> impl Iterator<Item = (u64, &[u8])> {
self.segments
.iter()
.filter(|(_, bytes)| !bytes.is_empty())
.map(|(address, bytes)| (*address, &self.bytes[bytes.clone()]))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub address: u64,
pub bytes: Vec<u8>,
}
pub fn place(
images: &[Image],
memory: &[Region],
spec: &CoreSpec,
boot: Option<u64>,
) -> Result<Vec<Segment>, Error> {
let alignment = spec.boot.alignment.get();
if let Some(at) = boot.filter(|at| at % alignment != 0) {
return Err(Error::Misaligned { at, alignment });
}
let mut placed = Vec::new();
for image in images {
for (address, bytes) in image.segments() {
let length = bytes.len() as u64;
let outside = || Error::Outside {
path: image.path.clone(),
start: address,
end: address.saturating_add(length),
};
let start = held(memory, &spec.aliases, address, length).ok_or_else(outside)?;
placed.push(Span {
path: image.path.clone(),
start,
end: start + length,
});
}
}
placed.sort_by_key(|segment| segment.start);
if let Some(pair) = placed.windows(2).find(|pair| pair[1].start < pair[0].end) {
return Err(Error::Overlap {
first: pair[0].clone(),
second: pair[1].clone(),
at: pair[1].start,
});
}
let at = boot.unwrap_or(spec.boot.reset);
let held = held(memory, &spec.aliases, at, 1);
if !held.is_some_and(|held| {
placed
.iter()
.any(|segment| (segment.start..segment.end).contains(&held))
}) {
return Err(Error::Unbooted { at, held });
}
Ok(images
.iter()
.flat_map(Image::segments)
.map(|(address, bytes)| Segment {
address,
bytes: bytes.to_vec(),
})
.collect())
}
fn held(memory: &[Region], aliases: &[Alias], address: u64, length: u64) -> Option<u64> {
let end = address.checked_add(length)?;
let inside = |base: u64, size: u64| base <= address && base.checked_add(size) >= Some(end);
memory
.iter()
.find(|region| inside(region.base, region.size))
.map(|_| address)
.or_else(|| {
aliases.iter().find_map(|alias| {
let region = memory.iter().find(|region| region.name == alias.region)?;
inside(alias.at, region.size).then(|| region.base + (address - alias.at))
})
})
}
#[must_use]
pub fn digest(images: &[Image], boot: Option<u64>) -> [u8; 32] {
fn address(hash: &mut Sha256, at: Option<u64>) {
match at {
Some(at) => {
hash.update([1]);
hash.update(at.to_le_bytes());
}
None => hash.update([0]),
}
}
let mut hash = Sha256::new();
for image in images {
hash.update((image.bytes.len() as u64).to_le_bytes());
hash.update(&image.bytes);
address(&mut hash, image.at);
}
address(&mut hash, boot);
hash.finalize().into()
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU64;
use super::*;
use crate::elf::built;
use crate::emulator::Boot;
const LOAD: u64 = 1;
const FLASH: u64 = 0x0800_0000;
const RAM: u64 = 0x2000_0000;
const MEMORY: [Region; 2] = [
Region::flash("flash", FLASH, 0x10_0000),
Region::ram("ram", RAM, 0x1_0000),
];
fn spec() -> CoreSpec {
CoreSpec {
processor: |_, _| Err(crate::emulator::Error::Machine("no core".to_owned())),
cpu: "test-cpu",
properties: Vec::new(),
instructions_per_second: NonZeroU64::new(250_000_000).unwrap(),
aliases: vec![Alias {
region: "flash",
at: 0,
}],
boot: Boot {
reset: 0,
alignment: NonZeroU64::new(512).unwrap(),
},
}
}
fn elf(name: &str, at: u64, length: usize) -> Image {
let code = vec![0xC0; length];
let file = built(
1,
1,
&[
(LOAD, at, at, &code, length as u64),
(LOAD, at + length as u64, RAM, &[0xDA; 8], 8),
(LOAD, RAM + 8, RAM + 8, &[], 0x100),
],
&[],
);
Image::new(Path::new(name), file, None).unwrap()
}
fn raw(name: &str, at: u64, length: usize) -> Image {
Image::new(Path::new(name), vec![0xB1; length], Some(at)).unwrap()
}
#[test]
fn an_elf_file_places_its_bytes_where_they_are_stored_and_nothing_it_zeroes() {
let placed = place(&[elf("app.elf", FLASH, 16)], &MEMORY, &spec(), None).unwrap();
assert_eq!(
placed,
[
Segment {
address: FLASH,
bytes: vec![0xC0; 16]
},
Segment {
address: FLASH + 16,
bytes: vec![0xDA; 8]
},
]
);
}
#[test]
fn a_bootloader_and_the_application_behind_it_both_place_though_both_zero_the_same_ram() {
let images = [
elf("bootloader.elf", FLASH, 0x3000),
elf("app.elf", FLASH + 0x4000, 0x100),
];
let placed = place(&images, &MEMORY, &spec(), None).unwrap();
assert_eq!(
placed
.iter()
.map(|segment| segment.address)
.collect::<Vec<_>>(),
[FLASH, FLASH + 0x3000, FLASH + 0x4000, FLASH + 0x4100]
);
}
#[test]
fn a_file_that_isnt_elf_goes_whole_at_its_address_and_needs_one() {
let placed = place(&[raw("app.bin", FLASH, 4)], &MEMORY, &spec(), None).unwrap();
assert_eq!(
placed,
[Segment {
address: FLASH,
bytes: vec![0xB1; 4]
}]
);
let unaddressed = Image::new(Path::new("app.bin"), vec![0xB1; 4], None).unwrap_err();
assert_eq!(
unaddressed.to_string(),
"app.bin isn't an ELF file, so it needs an address to go at"
);
let file = elf("app.elf", FLASH, 4).bytes().to_vec();
let addressed = Image::new(Path::new("app.elf"), file, Some(FLASH)).unwrap_err();
assert_eq!(
addressed.to_string(),
"app.elf is an ELF file, which says where its bytes go, so give it no address"
);
}
#[test]
fn two_images_that_overlap_are_refused_with_both_named_even_through_an_alias() {
let overlap = |images: &[Image]| {
place(images, &MEMORY, &spec(), None)
.unwrap_err()
.to_string()
};
assert_eq!(
overlap(&[
elf("bootloader.elf", FLASH, 0x4010),
raw("app.bin", FLASH + 0x4000, 0x100),
]),
"bootloader.elf at 0x8000000 to 0x8004010 and app.bin at 0x8004000 to 0x8004100 \
overlap from 0x8004000"
);
assert_eq!(
overlap(&[raw("low.bin", 0, 0x10), raw("high.bin", FLASH + 8, 0x10)]),
"low.bin at 0x8000000 to 0x8000010 and high.bin at 0x8000008 to 0x8000018 overlap \
from 0x8000008",
"an image placed at the second address flash answers at is in flash"
);
}
#[test]
fn a_segment_no_one_region_holds_is_refused() {
let outside = |image: Image| {
place(&[raw("boot.bin", FLASH, 8), image], &MEMORY, &spec(), None)
.unwrap_err()
.to_string()
};
assert_eq!(
outside(raw("far.bin", 0x0900_0000, 4)),
"far.bin places 0x9000000 to 0x9000004, and no one memory region holds all of it"
);
assert_eq!(
outside(raw("long.bin", FLASH + 0xF_FFFC, 8)),
"long.bin places 0x80ffffc to 0x8100004, and no one memory region holds all of it"
);
}
#[test]
fn the_core_boots_where_it_does_on_its_own_unless_the_run_names_where() {
let app = [elf("app.elf", FLASH + 0x4000, 0x100)];
assert_eq!(
place(&app, &MEMORY, &spec(), None).unwrap_err().to_string(),
"the core boots from 0x0, which is 0x8000000, and no image places anything there"
);
assert!(place(&app, &MEMORY, &spec(), Some(FLASH + 0x4000)).is_ok());
assert_eq!(
place(&app, &MEMORY, &spec(), Some(FLASH + 0x8000))
.unwrap_err()
.to_string(),
"the core boots from 0x8008000, and no image places anything there"
);
assert_eq!(
place(&app, &MEMORY, &spec(), Some(FLASH + 0x4100))
.unwrap_err()
.to_string(),
"the core can't boot from 0x8004100, which isn't a multiple of 0x200"
);
}
#[test]
fn the_digest_moves_with_any_image_where_one_goes_or_where_the_core_boots() {
let (boot, app) = (raw("boot.bin", FLASH, 4), raw("app.bin", FLASH + 0x200, 4));
let first = digest(&[boot.clone(), app.clone()], None);
assert_eq!(first, digest(&[boot.clone(), app.clone()], None));
for other in [
digest(&[app.clone(), boot.clone()], None),
digest(&[boot.clone(), raw("app.bin", FLASH + 0x400, 4)], None),
digest(&[boot.clone(), app.clone()], Some(FLASH)),
digest(std::slice::from_ref(&boot), None),
] {
assert_ne!(first, other);
}
}
}