use std::{
borrow::Cow,
cmp::Ordering,
collections::HashMap,
fmt::{Debug, Formatter},
mem::take,
ops::AddAssign,
};
use object::{
Endianness,
Object as _,
ObjectSection as _,
elf::{SHT_INIT_ARRAY, SHT_PROGBITS},
read::elf::{ElfFile32 as ElfFile, SectionHeader},
};
use serde::{Deserialize, Serialize};
pub use self::metadata::Metadata;
use crate::{image_format::idf::IdfBootloaderFormat, target::Chip};
pub mod idf;
mod metadata;
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ImageFormatKind {
#[default]
EspIdf,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[non_exhaustive]
pub enum ImageFormat<'a> {
EspIdf(IdfBootloaderFormat<'a>),
}
impl<'a> ImageFormat<'a> {
pub fn flash_segments(self) -> Vec<Segment<'a>> {
match self {
ImageFormat::EspIdf(idf) => idf.flash_segments().collect(),
}
}
pub fn ota_segments(self) -> Vec<Segment<'a>> {
match self {
ImageFormat::EspIdf(idf) => idf.ota_segments().collect(),
}
}
pub fn metadata(&self) -> HashMap<&str, String> {
match self {
ImageFormat::EspIdf(idf) => idf.metadata(),
}
}
}
impl<'a> From<IdfBootloaderFormat<'a>> for ImageFormat<'a> {
fn from(idf: IdfBootloaderFormat<'a>) -> Self {
Self::EspIdf(idf)
}
}
#[derive(Default, Clone, Eq, Deserialize, Serialize)]
pub struct Segment<'a> {
pub addr: u32,
pub data: Cow<'a, [u8]>,
}
impl<'a> Segment<'a> {
pub fn new(addr: u32, data: &'a [u8]) -> Self {
Segment {
addr,
data: Cow::Borrowed(data),
}
}
pub fn split_off(&mut self, count: usize) -> Self {
if count < self.data.len() {
let (head, tail) = match take(&mut self.data) {
Cow::Borrowed(data) => {
let (head, tail) = data.split_at(count);
(Cow::Borrowed(head), Cow::Borrowed(tail))
}
Cow::Owned(mut data) => {
let tail = data.split_off(count);
(Cow::Owned(data), Cow::Owned(tail))
}
};
let new = Segment {
addr: self.addr,
data: head,
};
self.addr += count as u32;
self.data = tail;
new
} else {
let new = self.clone();
self.addr += self.size();
self.data = Cow::Borrowed(&[]);
new
}
}
pub fn size(&self) -> u32 {
self.data.len() as u32
}
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
pub fn data_mut(&mut self) -> &mut [u8] {
self.data.to_mut()
}
pub fn pad_align(&mut self, align: usize) {
let padding = (align - self.data.len() % align) % align;
if padding > 0 {
let mut data = take(&mut self.data).into_owned();
data.extend_from_slice(&[0; 4][0..padding]);
self.data = Cow::Owned(data);
}
}
pub fn borrow<'b>(&'b self) -> Segment<'b>
where
'a: 'b,
{
Segment {
addr: self.addr,
data: Cow::Borrowed(self.data.as_ref()),
}
}
}
impl AddAssign<&'_ [u8]> for Segment<'_> {
fn add_assign(&mut self, rhs: &'_ [u8]) {
let mut data = take(&mut self.data).into_owned();
data.extend_from_slice(rhs);
self.data = Cow::Owned(data);
}
}
#[allow(clippy::suspicious_op_assign_impl)]
impl AddAssign<&'_ Segment<'_>> for Segment<'_> {
fn add_assign(&mut self, rhs: &'_ Segment<'_>) {
let mut data = take(&mut self.data).into_owned();
data.resize((rhs.addr - self.addr) as usize, 0);
data.extend_from_slice(rhs.data());
self.data = Cow::Owned(data);
}
}
impl Debug for Segment<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CodeSegment")
.field("addr", &self.addr)
.field("size", &self.size())
.finish()
}
}
impl PartialEq for Segment<'_> {
fn eq(&self, other: &Self) -> bool {
self.addr.eq(&other.addr)
}
}
impl PartialOrd for Segment<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Segment<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.addr.cmp(&other.addr)
}
}
pub(crate) fn ram_segments<'a>(
chip: Chip,
elf: &'a ElfFile<'a>,
) -> impl Iterator<Item = Segment<'a>> {
segments(elf).filter(move |segment| !chip.addr_is_flash(segment.addr))
}
pub(crate) fn rom_segments<'a>(
chip: Chip,
elf: &'a ElfFile<'a>,
) -> impl Iterator<Item = Segment<'a>> {
segments(elf).filter(move |segment| chip.addr_is_flash(segment.addr))
}
fn segments<'a>(elf: &'a ElfFile<'a>) -> impl Iterator<Item = Segment<'a>> {
elf.sections()
.filter(|section| {
let header = section.elf_section_header();
let sh_type = header.sh_type(Endianness::Little);
section.size() > 0
&& (sh_type == SHT_PROGBITS || sh_type == SHT_INIT_ARRAY)
&& header.sh_offset.get(Endianness::Little) > 0
&& section.address() > 0
&& !is_empty(section.flags())
})
.flat_map(move |section| match section.data() {
Ok(data) => Some(Segment::new(section.address() as u32, data)),
_ => None,
})
}
fn is_empty(flags: object::SectionFlags) -> bool {
match flags {
object::SectionFlags::None => true,
object::SectionFlags::Elf { sh_flags } => sh_flags == 0,
_ => unreachable!(),
}
}
#[cfg(test)]
mod test {
use object::read::elf::ElfFile;
use super::segments;
#[test]
fn test_overlapping_sections_are_removed() {
let elf_data: Vec<u8> = std::fs::read(
"tests/data/esp_hal_binary_with_overlapping_defmt_and_embedded_test_sections",
)
.unwrap();
let elf = ElfFile::parse(elf_data.as_slice()).unwrap();
let segments = segments(&elf).collect::<Vec<_>>();
let expected = [
(0x3F400020, 256), (0x3F400120, 29152), (0x3FFB0000, 3716), (0x40080000, 1024), (0x40080400, 5088), (0x400D0020, 62654), ];
assert_eq!(segments.len(), expected.len());
for seg in segments {
let addr_and_len = (seg.addr, seg.size());
assert!(
expected.contains(&addr_and_len),
"Unexpected section: {addr_and_len:x?}"
)
}
}
}