use espflash::flasher::{FlashFrequency, FlashMode};
use object::{
Endianness, Object, ObjectSection, elf::FileHeader32, elf::FileHeader64, elf::PT_LOAD,
read::elf::ElfFile, read::elf::FileHeader, read::elf::ProgramHeader,
};
use probe_rs_target::{InstructionSet, MemoryRange};
use serde::{Deserialize, Serialize};
use std::{
fs::File,
path::{Path, PathBuf},
str::FromStr,
};
use super::*;
use crate::session::Session;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct BinOptions {
pub base_address: Option<u64>,
pub skip: u32,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct IdfOptions {
pub bootloader: Option<PathBuf>,
pub partition_table: Option<PathBuf>,
pub target_app_partition: Option<String>,
pub flash_mode: Option<FlashMode>,
pub flash_frequency: Option<FlashFrequency>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct ElfOptions {
pub skip_sections: Vec<String>,
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum FormatKind {
Bin,
Hex,
#[default]
Elf,
Idf,
Uf2,
}
impl FormatKind {
pub fn from_optional(s: Option<&str>) -> Result<Self, String> {
match s {
Some(format) => Self::from_str(format),
None => Ok(Self::default()),
}
}
}
impl FromStr for FormatKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match &s.to_lowercase()[..] {
"bin" | "binary" => Ok(Self::Bin),
"hex" | "ihex" | "intelhex" => Ok(Self::Hex),
"elf" => Ok(Self::Elf),
"uf2" => Ok(Self::Uf2),
"idf" | "esp-idf" | "espidf" => Ok(Self::Idf),
_ => Err(format!("Format '{s}' is unknown.")),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub enum Format {
Bin(BinOptions),
Hex,
Elf(ElfOptions),
Idf(IdfOptions),
Uf2,
}
impl Default for Format {
fn default() -> Self {
Format::Elf(ElfOptions::default())
}
}
impl From<FormatKind> for Format {
fn from(kind: FormatKind) -> Self {
match kind {
FormatKind::Bin => Format::Bin(BinOptions::default()),
FormatKind::Hex => Format::Hex,
FormatKind::Elf => Format::Elf(ElfOptions::default()),
FormatKind::Uf2 => Format::Uf2,
FormatKind::Idf => Format::Idf(IdfOptions::default()),
}
}
}
#[derive(Debug, thiserror::Error, docsplay::Display)]
pub enum FileDownloadError {
#[ignore_extra_doc_attributes]
Flash(#[from] FlashError),
IhexRead(#[from] ihex::ReaderError),
IO(#[from] std::io::Error),
Object(&'static str),
Elf(#[from] object::read::Error),
Idf(#[from] espflash::Error),
IdfUnsupported(String),
#[ignore_extra_doc_attributes]
NoLoadableSegments,
FlashSizeDetection(#[source] FlashError),
IncompatibleImage {
target: Vec<InstructionSet>,
image: InstructionSet,
},
IncompatibleImageChip {
target: String,
image_chips: Vec<String>,
},
Other(#[source] crate::Error),
}
fn print_instr_sets(instr_sets: &[InstructionSet]) -> String {
instr_sets
.iter()
.map(|instr_set| format!("{instr_set:?}"))
.collect::<Vec<_>>()
.join(", ")
}
#[derive(Default)]
#[non_exhaustive]
pub struct DownloadOptions<'p> {
pub progress: FlashProgress<'p>,
pub keep_unwritten_bytes: bool,
pub dry_run: bool,
pub do_chip_erase: bool,
pub skip_erase: bool,
pub preverify: bool,
pub verify: bool,
pub disable_double_buffering: bool,
pub preferred_algos: Vec<String>,
}
impl DownloadOptions<'_> {
pub fn new() -> Self {
Self::default()
}
}
pub fn build_loader(
session: &mut Session,
path: impl AsRef<Path>,
format: Format,
image_instruction_set: Option<InstructionSet>,
) -> Result<FlashLoader, FileDownloadError> {
let mut loader = session.target().flash_loader();
let mut file = File::open(path).map_err(FileDownloadError::IO)?;
loader.load_image(session, &mut file, format, image_instruction_set)?;
Ok(loader)
}
pub fn download_file(
session: &mut Session,
path: impl AsRef<Path>,
format: impl Into<Format>,
) -> Result<(), FileDownloadError> {
download_file_with_options(session, path, format, DownloadOptions::default())
}
pub fn download_file_with_options(
session: &mut Session,
path: impl AsRef<Path>,
format: impl Into<Format>,
options: DownloadOptions,
) -> Result<(), FileDownloadError> {
let loader = build_loader(session, path, format.into(), None)?;
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()
}
}
}
fn extract_from_elf_inner<'data, T: FileHeader>(
elf_header: &T,
binary: ElfFile<'_, T>,
elf_data: &'data [u8],
options: &ElfOptions,
) -> Result<Vec<ExtractedFlashData<'data>>, FileDownloadError> {
let endian = elf_header.endian()?;
let mut extracted_data = Vec::new();
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 = 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)) {
let name = section.name()?;
if options.skip_sections.iter().any(|skip| skip == name) {
tracing::info!("Skipping section: {:?}", name);
continue;
}
tracing::info!("Matching 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(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,
});
}
}
}
Ok(extracted_data)
}
pub(super) fn extract_from_elf<'a>(
elf_data: &'a [u8],
options: &ElfOptions,
) -> Result<Vec<ExtractedFlashData<'a>>, FileDownloadError> {
let file_kind = object::FileKind::parse(elf_data)?;
match file_kind {
object::FileKind::Elf32 => {
let elf_header = FileHeader32::<Endianness>::parse(elf_data)?;
let binary = object::read::elf::ElfFile::<FileHeader32<Endianness>>::parse(elf_data)?;
extract_from_elf_inner(elf_header, binary, elf_data, options)
}
object::FileKind::Elf64 => {
let elf_header = FileHeader64::<Endianness>::parse(elf_data)?;
let binary = object::read::elf::ElfFile::<FileHeader64<Endianness>>::parse(elf_data)?;
extract_from_elf_inner(elf_header, binary, elf_data, options)
}
_ => Err(FileDownloadError::Object("Unsupported file type")),
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::FormatKind;
#[test]
fn parse_format() {
assert_eq!(FormatKind::from_str("hex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("Hex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("Ihex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("IHex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("iHex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("IntelHex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("intelhex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("intelHex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("Intelhex"), Ok(FormatKind::Hex));
assert_eq!(FormatKind::from_str("bin"), Ok(FormatKind::Bin));
assert_eq!(FormatKind::from_str("Bin"), Ok(FormatKind::Bin));
assert_eq!(FormatKind::from_str("binary"), Ok(FormatKind::Bin));
assert_eq!(FormatKind::from_str("Binary"), Ok(FormatKind::Bin));
assert_eq!(FormatKind::from_str("Elf"), Ok(FormatKind::Elf));
assert_eq!(FormatKind::from_str("elf"), Ok(FormatKind::Elf));
assert_eq!(FormatKind::from_str("idf"), Ok(FormatKind::Idf));
assert_eq!(FormatKind::from_str("espidf"), Ok(FormatKind::Idf));
assert_eq!(FormatKind::from_str("esp-idf"), Ok(FormatKind::Idf));
assert_eq!(FormatKind::from_str("ESP-IDF"), Ok(FormatKind::Idf));
assert_eq!(
FormatKind::from_str("elfbin"),
Err("Format 'elfbin' is unknown.".to_string())
);
assert_eq!(
FormatKind::from_str(""),
Err("Format '' is unknown.".to_string())
);
assert_eq!(
FormatKind::from_str("asdasdf"),
Err("Format 'asdasdf' is unknown.".to_string())
);
}
}