use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
use crate::elf::{self, File, Machine};
use crate::emulator::{Alias, CoreSpec};
use crate::soc::{Endian, Extent, Region, first_overlap};
#[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("{} is built for {file}, {}, and the core runs {core}, {}", path.display(), order(*file_endian), order(*part_endian))]
OtherMachine {
path: PathBuf,
file: Machine,
file_endian: Endian,
core: Machine,
part_endian: Endian,
},
#[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 order(endian: Endian) -> &'static str {
match endian {
Endian::Little => "little-endian",
Endian::Big => "big-endian",
}
}
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>,
built: Option<(Machine, Endian)>,
}
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 elf = |source| Error::Elf {
path: path.to_owned(),
source,
};
let (segments, built) = match (File::new(&bytes), at) {
(Ok(file), None) => {
let endian = if file.big_endian() {
Endian::Big
} else {
Endian::Little
};
let segments = file
.loads()
.map_err(elf)?
.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();
(segments, Some((file.machine().map_err(elf)?, endian)))
}
(Ok(_), Some(_)) => {
return Err(Error::Addressed {
path: path.to_owned(),
});
}
(Err(_), Some(at)) => (vec![(at, 0..bytes.len())], None),
(Err(_), None) => {
return Err(Error::Unaddressed {
path: path.to_owned(),
});
}
};
Ok(Self {
path: path.to_owned(),
bytes,
segments,
at,
built,
})
}
#[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,
endian: Endian,
boot: Option<u64>,
) -> Result<Vec<Segment>, Error> {
for image in images {
let Some((file, file_endian)) = image.built else {
continue;
};
let core = spec.elf.unwrap_or(file);
if file != core || file_endian != endian {
return Err(Error::OtherMachine {
path: image.path.clone(),
file,
file_endian,
core,
part_endian: endian,
});
}
}
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,
});
}
}
let spans = placed.iter().enumerate().map(|(index, segment)| Extent {
name: index,
base: segment.start,
size: segment.end - segment.start,
});
if let Some((first, second)) = first_overlap(spans) {
let (first, second) = (placed[first].clone(), placed[second].clone());
let at = second.start;
return Err(Error::Overlap { first, second, at });
}
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 std::sync::Arc;
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 {
let mut spec = CoreSpec::new(
Arc::new(|_| Err(crate::emulator::Error::Machine("no core".to_owned()))),
NonZeroU64::new(250_000_000).unwrap(),
Boot::new(0, NonZeroU64::new(512).unwrap()),
);
spec.aliases = vec![Alias {
region: "flash",
at: 0,
}];
spec
}
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(),
Endian::Little,
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(), Endian::Little, 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(),
Endian::Little,
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(), Endian::Little, 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(),
Endian::Little,
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(), Endian::Little, None)
.unwrap_err()
.to_string(),
"the core boots from 0x0, which is 0x8000000, and no image places anything there"
);
assert!(place(&app, &MEMORY, &spec(), Endian::Little, Some(FLASH + 0x4000)).is_ok());
assert_eq!(
place(&app, &MEMORY, &spec(), Endian::Little, Some(FLASH + 0x8000))
.unwrap_err()
.to_string(),
"the core boots from 0x8008000, and no image places anything there"
);
assert_eq!(
place(&app, &MEMORY, &spec(), Endian::Little, Some(FLASH + 0x4100))
.unwrap_err()
.to_string(),
"the core can't boot from 0x8004100, which isn't a multiple of 0x200"
);
}
#[test]
fn an_elf_file_for_another_machine_or_byte_order_is_refused() {
let mut named = spec();
named.elf = Some(Machine::new(0xBEEF, false));
let mut app = elf("app.elf", 0, 16).bytes().to_vec();
app[18..20].copy_from_slice(&0xBEEF_u16.to_le_bytes());
let app = Image::new(Path::new("app.elf"), app, None).unwrap();
assert!(
place(
std::slice::from_ref(&app),
&MEMORY,
&named,
Endian::Little,
None
)
.is_ok()
);
let other = elf("other.elf", 0, 16);
assert_eq!(
place(
std::slice::from_ref(&other),
&MEMORY,
&named,
Endian::Little,
None
)
.unwrap_err()
.to_string(),
"other.elf is built for ELF machine 0 in 32-bit files, little-endian, and the core runs \
ELF machine 48879 in 32-bit files, little-endian"
);
assert!(place(&[other], &MEMORY, &spec(), Endian::Little, None).is_ok());
assert!(matches!(
place(&[app], &MEMORY, &named, Endian::Big, None),
Err(Error::OtherMachine {
file_endian: Endian::Little,
part_endian: Endian::Big,
..
})
));
let wide = Image::new(
Path::new("wide.elf"),
built(2, 1, &[(LOAD, 0, 0, &[0xC0; 16], 16)], &[]),
None,
)
.unwrap();
assert!(matches!(
place(&[wide], &MEMORY, &named, Endian::Little, None),
Err(Error::OtherMachine { file, .. }) if file.wide
));
assert!(place(&[raw("app.bin", 0, 4)], &MEMORY, &named, Endian::Big, None).is_ok());
}
#[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);
}
}
}