target_gen/
algorithm_binary.rs

1use goblin::{
2    elf::program_header::PT_LOAD,
3    elf64::section_header::{SHT_NOBITS, SHT_PROGBITS},
4};
5use probe_rs_target::MemoryRange;
6
7use anyhow::{Result, anyhow};
8
9const CODE_SECTION_KEY: (&str, u32) = ("PrgCode", SHT_PROGBITS);
10const DATA_SECTION_KEY: (&str, u32) = ("PrgData", SHT_PROGBITS);
11const BSS_SECTION_KEY: (&str, u32) = ("PrgData", SHT_NOBITS);
12
13/// List of "suspicious" section names
14///
15/// These sections are usually present in Rust/C binaries,
16/// but should not be present in flash loader binaries.
17///
18/// If these are observed in the binary, we issue a warning.
19const SUSPICIOUS_SECTION_NAMES: &[&str] = &[".text", ".rodata", ".data", ".sdata", ".bss", ".sbss"];
20
21/// An ELF section of the flash algorithm ELF.
22#[derive(Debug, Clone)]
23pub(crate) struct Section {
24    pub(crate) start: u32,
25    pub(crate) length: u32,
26    pub(crate) data: Vec<u8>,
27
28    /// Load address for this section.
29    ///
30    /// For position independent code, this will not be used.
31    pub(crate) load_address: u32,
32}
33
34/// A struct to hold all the binary sections of a flash algorithm ELF that go into flash.
35#[derive(Debug, Clone)]
36pub(crate) struct AlgorithmBinary {
37    pub(crate) code_section: Section,
38    pub(crate) data_section: Section,
39    pub(crate) bss_section: Section,
40}
41
42impl AlgorithmBinary {
43    /// Extract a new flash algorithm binary blob from an ELF data blob.
44    pub(crate) fn new(elf: &goblin::elf::Elf<'_>, buffer: &[u8]) -> Result<Self> {
45        let mut code_section = None;
46        let mut data_section = None;
47        let mut bss_section = None;
48
49        let mut suspicious_sections = Vec::new();
50
51        // Iterate all program headers and get sections.
52        for ph in &elf.program_headers {
53            // Only regard sections that contain at least one byte.
54            // And are marked loadable (this filters out debug symbols).
55            if ph.p_type == PT_LOAD && ph.p_memsz > 0 {
56                let sector = ph.p_offset..ph.p_offset + ph.p_memsz;
57
58                log::debug!("Program header: LOAD to VMA {:#010x}", ph.p_vaddr);
59
60                // Scan all sectors if they contain any part of the sections found.
61                for sh in &elf.section_headers {
62                    let range = sh.sh_offset..sh.sh_offset + sh.sh_size;
63                    if sector.contains_range(&range) {
64                        // If we found a valid section, store its contents if any.
65                        let data = if sh.sh_type == SHT_NOBITS {
66                            Vec::new()
67                        } else {
68                            Vec::from(&buffer[sh.sh_offset as usize..][..sh.sh_size as usize])
69                        };
70
71                        let section = Some(Section {
72                            start: sh.sh_addr as u32,
73                            length: sh.sh_size as u32,
74                            data,
75                            load_address: (ph.p_vaddr + sh.sh_offset - ph.p_offset) as u32,
76                        });
77
78                        // Make sure we store the section contents under the right name.
79                        match (&elf.shdr_strtab[sh.sh_name], sh.sh_type) {
80                            CODE_SECTION_KEY => code_section = section,
81                            DATA_SECTION_KEY => data_section = section,
82                            BSS_SECTION_KEY => bss_section = section,
83                            (name, _section_type) => {
84                                if SUSPICIOUS_SECTION_NAMES.contains(&name) {
85                                    suspicious_sections.push(name);
86                                }
87                            }
88                        }
89                    }
90                }
91            }
92        }
93
94        if !suspicious_sections.is_empty() {
95            log::warn!(
96                "The ELF file contains some unexpected sections, which should not be part of a flash loader: "
97            );
98
99            for section in suspicious_sections {
100                log::warn!("\t{section}");
101            }
102
103            log::warn!(
104                "Code should be placed in the '{}' section, and data should be placed in the '{}' section.",
105                CODE_SECTION_KEY.0,
106                DATA_SECTION_KEY.0
107            );
108        }
109
110        // Check all the sections for validity and return the binary blob if possible.
111        let code_section = code_section.ok_or_else(|| {
112            anyhow!(
113                "Section '{}' not found, which is required to be present.",
114                CODE_SECTION_KEY.0
115            )
116        })?;
117
118        let data_section = data_section.unwrap_or_else(|| Section {
119            start: code_section.start + code_section.length,
120            length: 0,
121            data: Vec::new(),
122            load_address: code_section.load_address + code_section.length,
123        });
124
125        let zi_start = data_section.start + data_section.length;
126        let zi_address = data_section.load_address + data_section.length;
127
128        Ok(Self {
129            code_section,
130            data_section,
131            bss_section: bss_section.unwrap_or_else(|| Section {
132                start: zi_start,
133                length: 0,
134                data: Vec::new(),
135                load_address: zi_address,
136            }),
137        })
138    }
139
140    /// Assembles one huge binary blob as u8 values to write to RAM from the three sections.
141    pub(crate) fn blob(&self) -> Vec<u8> {
142        let mut blob = Vec::new();
143
144        blob.extend(&self.code_section.data);
145        blob.extend(&self.data_section.data);
146        blob.extend(&vec![0; self.bss_section.length as usize]);
147
148        blob
149    }
150
151    /// The current implementation assumes that all three sections follow each other
152    /// directly in RAM.
153    ///
154    /// This is especially important when the code is not position independent,
155    /// since it depends on the linker in this case. If the code *is* position independent,
156    /// it can be freely rearranged, and this is not an issue.
157    pub(crate) fn is_continuous_in_ram(&self) -> bool {
158        (self.code_section.load_address + self.code_section.length
159            == self.data_section.load_address)
160            && (self.data_section.load_address + self.data_section.length
161                == self.bss_section.load_address)
162    }
163}