elf2flash_core/
elf.rs

1use crate::{
2    address_range::{self, AddressRange, RP2040_ADDRESS_RANGES_FLASH, RP2040_ADDRESS_RANGES_RAM},
3};
4use assert_into::AssertInto;
5use log::debug;
6use std::{
7    cmp::min,
8    collections::BTreeMap,
9    error::Error,
10    io::{Read, Seek, SeekFrom},
11    mem,
12};
13use zerocopy::{FromBytes, IntoBytes};
14
15const ELF_MAGIC: u32 = 0x464c457f;
16const PT_LOAD: u32 = 0x00000001;
17
18pub const LOG2_PAGE_SIZE: u32 = 8;
19pub const PAGE_SIZE: u32 = 1 << LOG2_PAGE_SIZE;
20
21#[allow(unused)]
22#[repr(packed)]
23#[derive(IntoBytes, Copy, Clone, Default, Debug, FromBytes)]
24pub struct ElfHeader {
25    pub magic: u32,
26    pub arch_class: u8,
27    pub endianness: u8,
28    pub version: u8,
29    pub abi: u8,
30    pub abi_version: u8,
31    pub pad: [u8; 7],
32    pub typ: u16,
33    pub machine: u16,
34    pub version2: u32,
35}
36
37#[allow(unused)]
38#[repr(packed)]
39#[derive(IntoBytes, Copy, Clone, Default, Debug, FromBytes)]
40pub struct Elf32Header {
41    pub common: ElfHeader,
42    pub entry: u32,
43    pub ph_offset: u32,
44    pub sh_offset: u32,
45    pub flags: u32,
46    pub eh_size: u16,
47    pub ph_entry_size: u16,
48    pub ph_num: u16,
49    pub sh_entry_size: u16,
50    pub sh_num: u16,
51    pub sh_str_index: u16,
52}
53
54impl Elf32Header {
55    // read_and_check_elf32_header
56    pub(crate) fn from_read(input: &mut impl Read) -> Result<Self, Box<dyn Error>> {
57        let mut eh = Elf32Header::default();
58
59        input.read_exact(eh.as_mut_bytes())?;
60
61        if eh.common.magic != ELF_MAGIC {
62            return Err("Not an ELF file".into());
63        }
64        if eh.common.version != 1 || eh.common.version2 != 1 {
65            return Err("Unrecognized ELF version".into());
66        }
67        if eh.common.arch_class != 1 || eh.common.endianness != 1 {
68            return Err("Require 32 bit little-endian ELF".into());
69        }
70        if eh.eh_size != mem::size_of::<Elf32Header>().assert_into() {
71            return Err("Invalid ELF32 format".into());
72        }
73        if eh.common.abi != 0 && eh.common.abi != 3 {
74            return Err("Unrecognized ABI".into());
75        }
76
77        Ok(eh)
78    }
79
80    pub(crate) fn read_elf32_ph_entries(
81        &self,
82        input: &mut impl Read,
83    ) -> Result<Vec<Elf32PhEntry>, Box<dyn Error>> {
84        if self.ph_entry_size != mem::size_of::<Elf32PhEntry>().assert_into() {
85            return Err("Invalid ELF32 program header".into());
86        }
87
88        let mut entries: Vec<Elf32PhEntry> = (0..self.ph_num).map(|_| Default::default()).collect();
89        input.read_exact(entries.as_mut_slice().as_mut_bytes())?;
90
91        Ok(entries)
92    }
93
94    // "determine_binary_type"
95    pub(crate) fn is_ram_binary(&self, entries: &[Elf32PhEntry]) -> Option<bool> {
96        for entry in entries {
97            if entry.typ == PT_LOAD && entry.memsz > 0 {
98                let mapped_size = entry.filez.min(entry.memsz);
99                if mapped_size > 0 {
100                    // We back-convert the entrypoint from a VADDR to a PADDR to see if it originates inflash, and if
101                    // so call THAT a flash binary
102                    if self.entry >= entry.vaddr && self.entry < entry.vaddr + mapped_size {
103                        let effective_entry = self.entry + entry.paddr - entry.vaddr;
104                        if RP2040_ADDRESS_RANGES_RAM.is_address_initialized(effective_entry) {
105                            return Some(true);
106                        } else if RP2040_ADDRESS_RANGES_FLASH
107                            .is_address_initialized(effective_entry)
108                        {
109                            return Some(false);
110                        }
111                    }
112                }
113            }
114        }
115
116        None
117    }
118}
119
120#[allow(unused)]
121#[repr(packed)]
122#[derive(IntoBytes, Copy, Clone, Default, Debug, FromBytes)]
123pub struct Elf32PhEntry {
124    pub typ: u32,
125    pub offset: u32,
126    pub vaddr: u32,
127    pub paddr: u32,
128    pub filez: u32,
129    pub memsz: u32,
130    pub flags: u32,
131    pub align: u32,
132}
133
134#[derive(Copy, Clone, Debug, Default)]
135pub struct PageFragment {
136    pub file_offset: u32,
137    pub page_offset: u32,
138    pub bytes: u32,
139}
140
141pub fn realize_page(
142    input: &mut (impl Read + Seek),
143    fragments: &[PageFragment],
144    buf: &mut [u8],
145) -> Result<(), Box<dyn Error>> {
146    assert!(buf.len() >= PAGE_SIZE.assert_into());
147
148    for frag in fragments {
149        assert!(frag.page_offset < PAGE_SIZE && frag.page_offset + frag.bytes <= PAGE_SIZE);
150
151        input.seek(SeekFrom::Start(frag.file_offset.assert_into()))?;
152
153        input.read_exact(
154            &mut buf[frag.page_offset.assert_into()..(frag.page_offset + frag.bytes).assert_into()],
155        )?;
156    }
157
158    Ok(())
159}
160
161pub trait AddressRangesExt<'a>: IntoIterator<Item = &'a AddressRange> + Clone {
162    fn range_for(&self, addr: u32) -> Option<&'a AddressRange> {
163        self.clone()
164            .into_iter()
165            .find(|r| r.from <= addr && r.to > addr)
166    }
167
168    fn is_address_initialized(&self, addr: u32) -> bool {
169        let range = if let Some(range) = self.range_for(addr) {
170            range
171        } else {
172            return false;
173        };
174
175        matches!(range.typ, address_range::AddressRangeType::Contents)
176    }
177
178    // "check_address_range"
179    fn check_address_range(
180        &self,
181        addr: u32,
182        vaddr: u32,
183        size: u32,
184        uninitialized: bool,
185    ) -> Result<AddressRange, Box<dyn Error>> {
186        for range in self.clone().into_iter() {
187            if range.from <= addr && range.to >= addr + size {
188                if range.typ == address_range::AddressRangeType::NoContents && !uninitialized {
189                    return Err(format!(
190                        "ELF contains memory contents for uninitialized memory at {addr:08x}"
191                    )
192                    .into());
193                }
194
195                debug!(
196                    "{} segment {:#08x}->{:#08x} ({:#08x}->{:#08x})",
197                    if uninitialized {
198                        "Uninitialized"
199                    } else {
200                        "Mapped"
201                    },
202                    addr,
203                    addr + size,
204                    vaddr,
205                    vaddr + size
206                );
207                return Ok(*range);
208            }
209        }
210        Err(format!(
211            "Memory segment {:#08x}->{:#08x} is outside of valid address range for device",
212            addr,
213            addr + size
214        )
215        .into())
216    }
217
218    fn check_elf32_ph_entries(
219        &self,
220        entries: &[Elf32PhEntry],
221    ) -> Result<BTreeMap<u32, Vec<PageFragment>>, Box<dyn Error>> {
222        let mut pages = BTreeMap::<u32, Vec<PageFragment>>::new();
223
224        for entry in entries {
225            if entry.typ == PT_LOAD && entry.memsz > 0 {
226                let mapped_size = min(entry.filez, entry.memsz);
227
228                if mapped_size > 0 {
229                    let ar =
230                        self.check_address_range(entry.paddr, entry.vaddr, mapped_size, false)?;
231
232                    // we don't download uninitialized, generally it is BSS and should be zero-ed by crt0.S, or it may be COPY areas which are undefined
233                    if ar.typ != address_range::AddressRangeType::Contents {
234                        debug!("ignored");
235                        continue;
236                    }
237                    let mut addr = entry.paddr;
238                    let mut remaining = mapped_size;
239                    let mut file_offset = entry.offset;
240                    while remaining > 0 {
241                        let off = addr & (PAGE_SIZE - 1);
242                        let len = min(remaining, PAGE_SIZE - off);
243
244                        // list of fragments
245                        let fragments = pages.entry(addr - off).or_default();
246
247                        // note if filesz is zero, we want zero init which is handled because the
248                        // statement above creates an empty page fragment list
249                        // check overlap with any existing fragments
250                        for fragment in fragments.iter() {
251                            if (off < fragment.page_offset + fragment.bytes)
252                                != ((off + len) <= fragment.page_offset)
253                            {
254                                return Err("In memory segments overlap".into());
255                            }
256                        }
257                        fragments.push(PageFragment {
258                            file_offset,
259                            page_offset: off,
260                            bytes: len,
261                        });
262                        addr += len;
263                        file_offset += len;
264                        remaining -= len;
265                    }
266                    if entry.memsz > entry.filez {
267                        // we have some uninitialized data too
268                        self.check_address_range(
269                            entry.paddr + entry.filez,
270                            entry.vaddr + entry.filez,
271                            entry.memsz - entry.filez,
272                            true,
273                        )?;
274                    }
275                }
276            }
277        }
278
279        Ok(pages)
280    }
281}
282
283impl<'a, T> AddressRangesExt<'a> for T where T: IntoIterator<Item = &'a AddressRange> + Clone {}