use object::{
elf::FileHeader32, elf::PT_LOAD, read::elf::FileHeader, read::elf::ProgramHeader, Endianness,
Object, ObjectSection,
};
use probe_rs_target::MemoryRange;
use std::{fs::File, path::Path, str::FromStr};
use super::*;
use crate::session::Session;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct BinOptions {
pub base_address: Option<u64>,
pub skip: u32,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub enum Format {
Bin(BinOptions),
Hex,
Elf,
}
impl FromStr for Format {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match &s.to_lowercase()[..] {
"bin" | "binary" => Ok(Format::Bin(BinOptions {
base_address: None,
skip: 0,
})),
"hex" | "ihex" | "intelhex" => Ok(Format::Hex),
"elf" => Ok(Format::Elf),
_ => Err(format!("Format '{s}' is unknown.")),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum FileDownloadError {
#[error("Error while flashing")]
Flash(#[from] FlashError),
#[error("Could not read ihex format")]
IhexRead(#[from] ihex::ReaderError),
#[error("I/O error")]
IO(#[from] std::io::Error),
#[error("Object Error: {0}.")]
Object(&'static str),
#[error("Could not read ELF file")]
Elf(#[from] object::read::Error),
#[error("No loadable ELF sections were found.")]
NoLoadableSegments,
}
#[derive(Default)]
#[non_exhaustive]
pub struct DownloadOptions {
pub progress: Option<FlashProgress>,
pub keep_unwritten_bytes: bool,
pub dry_run: bool,
pub do_chip_erase: bool,
pub skip_erase: bool,
pub verify: bool,
pub disable_double_buffering: bool,
}
impl DownloadOptions {
pub fn new() -> Self {
Self::default()
}
}
pub fn download_file<P: AsRef<Path>>(
session: &mut Session,
path: P,
format: Format,
) -> Result<(), FileDownloadError> {
download_file_with_options(session, path, format, DownloadOptions::default())
}
pub fn download_file_with_options<P: AsRef<Path>>(
session: &mut Session,
path: P,
format: Format,
options: DownloadOptions,
) -> Result<(), FileDownloadError> {
let mut file = match File::open(path.as_ref()) {
Ok(file) => file,
Err(e) => return Err(FileDownloadError::IO(e)),
};
let mut loader = session.target().flash_loader();
match format {
Format::Bin(options) => loader.load_bin_data(&mut file, options),
Format::Elf => loader.load_elf_data(&mut file),
Format::Hex => loader.load_hex_data(&mut file),
}?;
loader
.commit(session, options)
.map_err(FileDownloadError::Flash)
}
pub(super) struct ExtractedFlashData<'data> {
pub(super) section_names: Vec<String>,
pub(super) address: u32,
pub(super) data: &'data [u8],
}
impl std::fmt::Debug for ExtractedFlashData<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut helper = f.debug_struct("ExtractedFlashData");
helper
.field("name", &self.section_names)
.field("address", &self.address);
if self.data.len() > 10 {
helper
.field("data", &format!("[..] ({} bytes)", self.data.len()))
.finish()
} else {
helper.field("data", &self.data).finish()
}
}
}
pub(super) fn extract_from_elf<'data>(
extracted_data: &mut Vec<ExtractedFlashData<'data>>,
elf_data: &'data [u8],
) -> Result<usize, FileDownloadError> {
let file_kind = object::FileKind::parse(elf_data)?;
match file_kind {
object::FileKind::Elf32 => (),
_ => return Err(FileDownloadError::Object("Unsupported file type")),
}
let elf_header = FileHeader32::<Endianness>::parse(elf_data)?;
let binary = object::read::elf::ElfFile::<FileHeader32<Endianness>>::parse(elf_data)?;
let endian = elf_header.endian()?;
let mut extracted_sections = 0;
for segment in elf_header.program_headers(elf_header.endian()?, elf_data)? {
let p_paddr: u64 = segment.p_paddr(endian).into();
let p_vaddr: u64 = segment.p_vaddr(endian).into();
let flags = segment.p_flags(endian);
let segment_data = segment
.data(endian, elf_data)
.map_err(|_| FileDownloadError::Object("Failed to access data for an ELF segment."))?;
let mut elf_section = Vec::new();
if !segment_data.is_empty() && segment.p_type(endian) == PT_LOAD {
tracing::info!(
"Found loadable segment, physical address: {:#010x}, virtual address: {:#010x}, flags: {:#x}",
p_paddr,
p_vaddr,
flags
);
let (segment_offset, segment_filesize) = segment.file_range(endian);
let sector: core::ops::Range<u64> = segment_offset..segment_offset + segment_filesize;
for section in binary.sections() {
let (section_offset, section_filesize) = match section.file_range() {
Some(range) => range,
None => continue,
};
if sector.contains_range(&(section_offset..section_offset + section_filesize)) {
tracing::info!("Matching section: {:?}", section.name()?);
#[cfg(feature = "hexdump")]
for line in hexdump::hexdump_iter(section.data()?) {
tracing::trace!("{}", line);
}
for (offset, relocation) in section.relocations() {
tracing::info!(
"Relocation: offset={}, relocation={:?}",
offset,
relocation
);
}
elf_section.push(section.name()?.to_owned());
}
}
if elf_section.is_empty() {
tracing::info!("Not adding segment, no matching sections found.");
} else {
let section_data =
&elf_data[segment_offset as usize..][..segment_filesize as usize];
extracted_data.push(ExtractedFlashData {
section_names: elf_section,
address: p_paddr as u32,
data: section_data,
});
extracted_sections += 1;
}
}
}
Ok(extracted_sections)
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::{BinOptions, Format};
#[test]
fn parse_format() {
assert_eq!(Format::from_str("hex"), Ok(Format::Hex));
assert_eq!(Format::from_str("Hex"), Ok(Format::Hex));
assert_eq!(Format::from_str("Ihex"), Ok(Format::Hex));
assert_eq!(Format::from_str("IHex"), Ok(Format::Hex));
assert_eq!(Format::from_str("iHex"), Ok(Format::Hex));
assert_eq!(Format::from_str("IntelHex"), Ok(Format::Hex));
assert_eq!(Format::from_str("intelhex"), Ok(Format::Hex));
assert_eq!(Format::from_str("intelHex"), Ok(Format::Hex));
assert_eq!(Format::from_str("Intelhex"), Ok(Format::Hex));
assert_eq!(
Format::from_str("bin"),
Ok(Format::Bin(BinOptions {
base_address: None,
skip: 0
}))
);
assert_eq!(
Format::from_str("Bin"),
Ok(Format::Bin(BinOptions {
base_address: None,
skip: 0
}))
);
assert_eq!(
Format::from_str("binary"),
Ok(Format::Bin(BinOptions {
base_address: None,
skip: 0
}))
);
assert_eq!(
Format::from_str("Binary"),
Ok(Format::Bin(BinOptions {
base_address: None,
skip: 0
}))
);
assert_eq!(Format::from_str("Elf"), Ok(Format::Elf));
assert_eq!(Format::from_str("elf"), Ok(Format::Elf));
assert_eq!(
Format::from_str("elfbin"),
Err("Format 'elfbin' is unknown.".to_string())
);
assert_eq!(
Format::from_str(""),
Err("Format '' is unknown.".to_string())
);
assert_eq!(
Format::from_str("asdasdf"),
Err("Format 'asdasdf' is unknown.".to_string())
);
}
}