Skip to main content

xous_tools/
elf.rs

1use std::convert::TryInto;
2use std::fmt;
3use std::fs::File;
4use std::io::{Cursor, Read, Seek, SeekFrom, Write};
5use std::path::Path;
6
7use bitflags::bitflags;
8use log::debug;
9use xmas_elf::ElfFile;
10use xmas_elf::program::Type as ProgramType;
11use xmas_elf::sections::ShType;
12// Normal ELF flags
13use xmas_elf::sections::{SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE};
14
15bitflags! {
16    pub struct MiniElfFlags: u8 {
17        const NONE = 0;
18        const WRITE = 1;
19        const NOCOPY = 2;
20        const EXECUTE = 4;
21        const EH_FRAME = 8;
22        const EH_HEADER = 0x10;
23    }
24}
25
26pub struct ProgramDescription {
27    /// Virtual address of .text section in RAM
28    pub text_offset: u32,
29
30    /// Size of the .text section in RAM
31    pub text_size: u32,
32
33    /// Virtual address of .data section in RAM
34    pub data_offset: u32,
35
36    /// Size of .data section
37    pub data_size: u32,
38
39    /// Poke table for data section; in (address, data) tuples. Addresses are in `u32` format
40    /// because we're packing them for a target that is 32-bits, which may be different from the host.
41    pub poke_table: Vec<(u32, u32)>,
42
43    /// Size of region to be zero-ized by the loader
44    pub clear_size: u32,
45
46    /// Size of the .bss section
47    pub bss_size: u32,
48
49    /// Virtual address of the entrypoint
50    pub entry_point: u32,
51
52    /// Program contents
53    pub program: Vec<u8>,
54}
55
56#[derive(Debug)]
57pub struct MiniElfSection {
58    pub virt: u32,
59    pub size: u32,
60    pub flags: MiniElfFlags,
61    pub name: String,
62}
63
64impl fmt::Display for MiniElfSection {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(
67            f,
68            "Section {:13} {:6} bytes loading into {:08x}..{:08x} flags: {:?}",
69            self.name,
70            self.size,
71            self.virt,
72            self.virt + self.size,
73            self.flags
74        )
75    }
76}
77
78/// Describes a Mini ELF file, suitable for loading into RAM
79pub struct MiniElf {
80    /// Virtual address of the entrypoint
81    pub entry_point: u32,
82
83    /// All of the sections inside this file
84    pub sections: Vec<MiniElfSection>,
85
86    /// Actual section data
87    pub program: Vec<u8>,
88
89    /// Alignment offset for page mapping
90    pub alignment_offset: usize,
91}
92
93#[derive(Debug)]
94pub enum ElfReadError {
95    /// Read an unexpected number of bytes
96    WrongReadSize(u64 /* expected */, u64 /* actual */),
97
98    /// "Couldn't seek to end of file"
99    SeekFromEndError(std::io::Error),
100
101    /// Couldn't read ELF file
102    ReadFileError(std::io::Error),
103
104    /// Couldn't open the ELF file
105    OpenElfError(std::io::Error),
106
107    /// Couldn't parse the ELF file
108    ParseElfError(&'static str),
109
110    /// Section wasn't in range
111    SectionRangeError,
112
113    /// Section wasn't word-aligned
114    SectionNotAligned(String /* section name */, usize /* section size */),
115
116    /// Couldn't seek the file to write the section
117    FileSeekError(std::io::Error),
118
119    /// Couldn't write the section to the file
120    WriteSectionError(std::io::Error),
121}
122
123impl fmt::Display for ElfReadError {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        use ElfReadError::*;
126        match self {
127            WrongReadSize(e, a) => {
128                write!(f, "expected to read {} bytes, but instead read {}", e, a)
129            }
130            SeekFromEndError(e) => write!(f, "couldn't seek from the end of the file: {}", e),
131            ReadFileError(e) => write!(f, "couldn't read from the file: {}", e),
132            OpenElfError(e) => write!(f, "couldn't open the elf file: {}", e),
133            ParseElfError(e) => write!(f, "couldn't parse the elf file: {}", e),
134            SectionRangeError => write!(f, "elf section pointed outside of the file"),
135            SectionNotAligned(s, a) => write!(f, "elf section {} had unaligned length {}", s, a),
136            FileSeekError(e) => write!(f, "couldn't seek in the output file: {}", e),
137            WriteSectionError(e) => write!(f, "couldn't write a section to the output file: {}", e),
138        }
139    }
140}
141
142#[allow(clippy::cognitive_complexity)]
143pub fn read_program<P: AsRef<Path>>(filename: P) -> Result<ProgramDescription, ElfReadError> {
144    let mut b = Vec::new();
145    {
146        let mut fi = File::open(filename).map_err(ElfReadError::OpenElfError)?;
147        fi.read_to_end(&mut b).map_err(ElfReadError::ReadFileError)?;
148    }
149    process_program(&b, false)
150}
151
152#[allow(clippy::cognitive_complexity)]
153pub fn read_loader<P: AsRef<Path>>(filename: P) -> Result<ProgramDescription, ElfReadError> {
154    let mut b = Vec::new();
155    {
156        let mut fi = File::open(filename).map_err(ElfReadError::OpenElfError)?;
157        fi.read_to_end(&mut b).map_err(ElfReadError::ReadFileError)?;
158    }
159    process_program(&b, true)
160}
161
162pub fn process_program(b: &[u8], rom_only: bool) -> Result<ProgramDescription, ElfReadError> {
163    let elf = ElfFile::new(&b).map_err(|x| ElfReadError::ParseElfError(x))?;
164    let entry_point = elf.header.pt2.entry_point() as u32;
165    let mut program_data = Cursor::new(Vec::new());
166    let mut poke_table = Vec::<(u32, u32)>::new();
167
168    let mut size = 0;
169    let mut data_offset = 0;
170    let mut data_size = 0;
171    let mut text_offset = 0;
172    let mut text_size = 0;
173    let mut bss_size = 0;
174    let mut phys_offset = 0;
175
176    debug!("ELF: {:?}", elf.header);
177    for ph in elf.program_iter() {
178        debug!("Program Header: {:?}", ph);
179        if ph.get_type() == Ok(ProgramType::Load) && phys_offset == 0 {
180            phys_offset = ph.physical_addr();
181        }
182        debug!("Physical address: {:08x}", ph.physical_addr());
183        debug!("Virtual address: {:08x}", ph.virtual_addr());
184        debug!("Offset: {:08x}", ph.offset());
185        debug!("Size: {:08x}", ph.file_size());
186    }
187    debug!("Program starts at 0x{:x}", entry_point);
188
189    let mut program_offset = 0;
190    let mut data_copy = Vec::new();
191    for s in elf.section_iter() {
192        let name = s.get_name(&elf).unwrap_or("<<error>>");
193
194        if s.address() == 0 {
195            debug!("(Skipping section {} -- invalid address)", name);
196            continue;
197        }
198
199        debug!("Section {}:", name);
200        debug!("Official header:");
201        debug!("{:x?}", s);
202        debug!("Interpreted:");
203        debug!("    flags:            {:?}", s.flags());
204        debug!("    type:             {:?}", s.get_type());
205        debug!("    address:          {:08x}", s.address());
206        debug!("    offset:           {:08x}", s.offset());
207        debug!("    size:             {:x?}", s.size());
208        debug!("    link:             {:?}", s.link());
209        size += s.size();
210        // Pad the section so it's a multiple of 4 bytes.
211        // It's unclear if this is necessary, since this seems to indicate
212        // that something has gone horribly wrong.
213        size += (4 - (size & 3)) & 3;
214        if size & 3 != 0 {
215            return Err(ElfReadError::SectionNotAligned(name.to_owned(), s.size() as usize));
216        }
217
218        if name == ".data" {
219            data_offset = s.address() as u32;
220            data_size += s.size() as u32;
221
222            if rom_only {
223                debug!(
224                    "\n-- Not writing {}, type: {:?} flags: {:x}, len: {:x} -- ROM image requested --\n",
225                    name,
226                    s.get_type(),
227                    s.flags(),
228                    s.size(),
229                );
230
231                // This flag in particular causes the data section to be skipped. This "must be" the case
232                // for the loader, because the loader doesn't have a loader. Thus as a requirement, the
233                // loader must have a data region that is all 0. Check that this condition is met.
234                let section_data = s.raw_data(&elf);
235                if !section_data.iter().all(|&x| x == 0) {
236                    // If you get this panic, this is why it happened, and what you need to do.
237                    //
238                    // The why: the loader itself doesn't have a loader. So, any .data required by
239                    // the loader program can't be set up in advance for the loader.
240                    //
241                    // What causes this: generally, a `static mut` in the loader will cause some .data
242                    // to be allocated. In the precursor/betrusted loader, there are no instances of this.
243                    //
244                    // However, in the baochip loaders, the USB handler needs to be a `static mut` because
245                    // the interrupt handler needs to be able to find it at a globally known location, and
246                    // the data has to persist beyond the scope of a single interrupt.
247                    //
248                    // Why we can skip it in the case of the loader: the reason we don't have to include
249                    // the data section in the loader's ROM image is two-fold. 1) the data going into the
250                    // `static mut` interrupt handler is assumed uninitialized (due to the wrapper being
251                    // an Option<Usb> set to None); and 2) the RAM is fully zeroized by a small assembly
252                    // routine that executes before the loader runs. (1) means that in practice, the contents
253                    // of the .data section is always 0. (2) means we can just whack a pointer at where the
254                    // data section should go and the assumptions are met for the loader.
255                    //
256                    // So, the `if` statement above assures us that we didn't do something like create
257                    // a `static mut` which has a non-zero value that program execution relies upon.
258                    //
259                    // The basic answer for the loader is "don't do that". Because the loader doesn't have
260                    // a loader, it needs to be self-sufficient in terms of setting up all of its state,
261                    // so in the case that some global shared state is needed, there should be an explicit
262                    // initializer somewhere in the code. If this panic triggers, look for the code that
263                    // is assuming some data is magically pre-loaded for the loader, and eliminate that code.
264                    data_copy.extend_from_slice(&section_data);
265
266                    /*
267                    println!("Loader data section is not all 0's. This case is not handled by the loader.");
268                    println!("Here is what is non-zero, as (byte offset: byte) tuples:");
269                    let mut printed = 0;
270                    for (i, &d) in section_data.iter().enumerate() {
271                        if d != 0 {
272                            printed += 1;
273                            println!("    ({:04x}: {:02x})", i, d);
274                        }
275                        if printed > 64 {
276                            println!("** Output cut off due to debug length limit");
277                            break;
278                        }
279                    }
280                    */
281                }
282                continue;
283            }
284        } else if s.get_type() == Ok(ShType::NoBits) {
285            // Add bss-type sections to the data section
286            bss_size += s.size() as u32;
287            debug!("Skipping copy of {} @ {:08x} because nobits", name, s.address());
288            continue;
289        } else if text_offset == 0 && (s.address() != 0 || s.size() != 0) {
290            text_offset = s.address() as u32;
291            text_size += s.size() as u32;
292        } else {
293            if text_offset + text_size != s.address() as u32 {
294                let bytes_to_add = s.address() - (text_offset + text_size) as u64;
295                debug!("Padding text size by {} bytes...", bytes_to_add);
296                program_data
297                    .seek(SeekFrom::Current(bytes_to_add as i64))
298                    .map_err(ElfReadError::FileSeekError)?;
299                text_size += bytes_to_add as u32;
300                program_offset += bytes_to_add as u64;
301                // panic!(
302                //     "size not correct!  should be {:08x}, was {:08x}, need to add {} bytes",
303                //     text_offset + text_size,
304                //     s.address(),
305                //     s.address() - (text_offset + text_size) as u64,
306                // );
307            }
308            text_size += s.size() as u32;
309        }
310        if s.size() == 0 {
311            debug!("Skipping {} because size is 0", name);
312            continue;
313        }
314        debug!("Adding {} to the file", name);
315        debug!(
316            "  s.offset: {:08x}  program_offset: {:08x}  Bytes: {:08x}",
317            s.offset(),
318            program_offset,
319            s.raw_data(&elf).len(),
320        );
321        let section_data = s.raw_data(&elf);
322        debug!(
323            "Section start: {:02x} {:02x} {:02x} {:02x} going into offset 0x{:08x}",
324            section_data[0], section_data[1], section_data[2], section_data[3], program_offset
325        );
326        program_data.seek(SeekFrom::Start(program_offset)).map_err(ElfReadError::FileSeekError)?;
327        program_data.write(section_data).map_err(ElfReadError::WriteSectionError)?;
328        program_offset += section_data.len() as u64;
329    }
330    let observed_size = program_data.seek(SeekFrom::End(0)).map_err(ElfReadError::SeekFromEndError)?;
331
332    debug!("Text size: {} bytes", text_size);
333    debug!("Text offset: {:08x}", text_offset);
334    debug!("Data size: {} bytes", data_size);
335    debug!("Data offset: {:08x}", data_offset);
336    debug!("Program size: {} bytes", observed_size);
337
338    if data_offset as usize % size_of::<u32>() == 0 {
339        for (i, chunk) in data_copy.chunks_exact(4).enumerate() {
340            let word = u32::from_le_bytes(chunk.try_into().unwrap());
341            if word != 0 {
342                poke_table.push(((i as u32) * 4, word));
343            }
344        }
345    } else {
346        println!(
347            "Data section is not word-aligned, check objdump in detail for how to initialize the section"
348        );
349    }
350    Ok(ProgramDescription {
351        entry_point,
352        program: program_data.into_inner(),
353        data_size,
354        data_offset,
355        text_offset,
356        text_size,
357        bss_size,
358        // round up to the nearest u32 word. Includes .data, .bss, .stack, .heap - regions to be zero'd.
359        clear_size: (((data_size + bss_size) as usize + size_of::<u32>() - 1) & !(size_of::<u32>() - 1))
360            as u32,
361        poke_table,
362    })
363}
364
365/// Read an ELF file into a mini ELF file.
366#[allow(clippy::cognitive_complexity)]
367pub fn read_minielf<P: AsRef<Path>>(filename: P) -> Result<MiniElf, ElfReadError> {
368    let mut b = Vec::new();
369    {
370        let mut fi = File::open(filename).map_err(ElfReadError::OpenElfError)?;
371        fi.read_to_end(&mut b).map_err(ElfReadError::ReadFileError)?;
372    }
373    process_minielf(&b)
374}
375
376pub fn process_minielf(b: &[u8]) -> Result<MiniElf, ElfReadError> {
377    let elf = ElfFile::new(&b).map_err(|x| ElfReadError::ParseElfError(x))?;
378    let entry_point = elf.header.pt2.entry_point() as u32;
379    let mut program_data = Cursor::new(Vec::new());
380    let mut alignment_offset = 0;
381
382    let mut sections = vec![];
383
384    debug!("ELF: {:?}", elf.header);
385    for ph in elf.program_iter() {
386        debug!("Program Header: {:?}", ph);
387        debug!("Physical address: {:08x}", ph.physical_addr());
388        debug!("Virtual address: {:08x}", ph.virtual_addr());
389        debug!("Offset: {:08x}", ph.offset());
390        debug!("Size: {:08x}", ph.file_size());
391    }
392    debug!("Program starts at 0x{:x}", entry_point);
393
394    // This keeps a running offset of where data is getting copied.
395    let mut program_offset = 0;
396    let mut section_iter = elf.section_iter().peekable();
397    let mut init_offset = 0;
398    while let Some(s) = section_iter.next() {
399        let mut flags = MiniElfFlags::NONE;
400        let name = s.get_name(&elf).unwrap_or("<<error>>");
401
402        if s.address() == 0 {
403            debug!("(Skipping section {} -- invalid address)", name);
404            // only extract the initial offset once per ELF file
405            if init_offset == 0 {
406                init_offset = if let Some(s) = section_iter.peek() { s.offset() } else { 0 };
407            }
408            continue;
409        }
410        if alignment_offset == 0 {
411            alignment_offset = s.address() & 0xFFF;
412        }
413
414        debug!("Section {}:", name);
415        debug!("{} official header: {:x?}", name, s);
416        debug!("Interpreted:");
417        debug!("    flags:            {:?}", s.flags());
418        debug!("    type:             {:?}", s.get_type());
419        debug!("    address:          {:08x}", s.address());
420        debug!("    offset:           {:08x}", s.offset());
421        debug!("    size:             {:?}", s.size());
422        debug!("    link:             {:?}", s.link());
423        let mut size = s.size();
424
425        let no_copy = s.get_type() == Ok(ShType::NoBits);
426
427        if s.flags() & SHF_ALLOC == 0 {
428            debug!("section has no allocations -- skipping");
429            continue;
430        }
431        if no_copy {
432            flags |= MiniElfFlags::NOCOPY;
433        }
434        if s.flags() & SHF_EXECINSTR != 0 {
435            flags |= MiniElfFlags::EXECUTE;
436        }
437        if s.flags() & SHF_WRITE != 0 {
438            flags |= MiniElfFlags::WRITE;
439        }
440        if name == ".eh_frame_hdr" {
441            flags |= MiniElfFlags::EH_HEADER
442        } else if name == ".eh_frame" {
443            flags |= MiniElfFlags::EH_FRAME;
444        }
445
446        debug!("Adding {} to the file", name);
447        debug!(
448            "{} offset: {:08x}  program_offset: {:08x}  bytes: {}  seek: {}",
449            name,
450            s.offset(),
451            program_offset,
452            if no_copy { 0 } else { s.raw_data(&elf).len() },
453            program_offset
454        );
455
456        // If this section gets copied, add it to the program stream.
457        if s.get_type() != Ok(ShType::NoBits) {
458            let section_data = s.raw_data(&elf);
459            let pad_amount = if let Some(next_section) = section_iter.peek() {
460                if (section_data.len() + program_offset as usize + init_offset as usize)
461                    % next_section.align() as usize
462                    != 0
463                {
464                    let pad_amount = next_section.align() as usize
465                        - ((section_data.len() + program_offset as usize + init_offset as usize)
466                            % next_section.align() as usize);
467                    if s.address() + size + pad_amount as u64 > next_section.address() {
468                        (next_section.address() - (s.address() + size)) as usize
469                    } else {
470                        pad_amount
471                    }
472                } else {
473                    0
474                }
475            } else {
476                0
477            };
478
479            debug!(
480                "Section start: {:02x} {:02x} {:02x} {:02x} going into offset 0x{:08x}",
481                section_data[0], section_data[1], section_data[2], section_data[3], program_offset
482            );
483            program_data.seek(SeekFrom::Start(program_offset)).map_err(ElfReadError::FileSeekError)?;
484            program_data.write(section_data).map_err(ElfReadError::WriteSectionError)?;
485            program_offset += section_data.len() as u64;
486
487            if pad_amount != 0 {
488                let pad = vec![0u8; pad_amount];
489                program_data.write(&pad).map_err(ElfReadError::WriteSectionError)?;
490                program_offset += pad_amount as u64;
491                size += pad_amount as u64;
492            }
493        } else {
494            // we leave the nocopy sections mis-aligned.
495            // They don't exist in the file, they are just zero'd on spec.
496            // This works so long as the nocopy sections are at the end of the MiniElf.
497        }
498        sections.push(MiniElfSection {
499            virt: s.address() as u32,
500            size: size as u32,
501            name: name.to_string(),
502            flags,
503        });
504    }
505    let observed_size = program_data.seek(SeekFrom::End(0)).map_err(ElfReadError::SeekFromEndError)?;
506
507    debug!("Program size: {} bytes", observed_size);
508    Ok(MiniElf {
509        entry_point,
510        sections,
511        program: program_data.into_inner(),
512        alignment_offset: alignment_offset as usize,
513    })
514}