1use std::fs;
2use std::path::Path;
3
4use serde::Serialize;
5
6use crate::memory::MemoryConfig;
7
8#[derive(Debug, Clone, PartialEq, Serialize)]
10pub enum RegionType {
11 Flash,
12 Ram,
13 Other,
14}
15
16#[derive(Debug, Clone, Serialize)]
18pub struct SectionInfo {
19 pub name: String,
21 pub address: u32,
23 pub size: u32,
25 pub flags: u32,
27 pub region: RegionType,
29}
30
31impl SectionInfo {
32 pub fn is_alloc(&self) -> bool {
33 self.flags & 0x2 != 0
34 }
35 pub fn is_writable(&self) -> bool {
36 self.flags & 0x1 != 0
37 }
38 pub fn is_executable(&self) -> bool {
39 self.flags & 0x4 != 0
40 }
41}
42
43#[derive(Debug, Clone, Serialize)]
45pub struct FirmwareUsage {
46 pub flash_used: u64,
48 pub ram_used: u64,
50 pub flash_total: u64,
52 pub ram_total: u64,
54}
55
56impl FirmwareUsage {
57 pub fn flash_percent(&self) -> f64 {
59 if self.flash_total == 0 {
60 0.0
61 } else {
62 self.flash_used as f64 * 100.0 / self.flash_total as f64
63 }
64 }
65
66 pub fn ram_percent(&self) -> f64 {
68 if self.ram_total == 0 {
69 0.0
70 } else {
71 self.ram_used as f64 * 100.0 / self.ram_total as f64
72 }
73 }
74
75 pub fn flash_free(&self) -> u64 {
77 self.flash_total.saturating_sub(self.flash_used)
78 }
79
80 pub fn ram_free(&self) -> u64 {
82 self.ram_total.saturating_sub(self.ram_used)
83 }
84}
85
86#[derive(Debug, Clone, Serialize)]
88pub struct ElfAnalysis {
89 pub usage: FirmwareUsage,
91 pub sections: Vec<SectionInfo>,
93}
94
95pub fn analyze_elf<P: AsRef<Path>>(elf_path: P, config: &MemoryConfig) -> Result<FirmwareUsage, String> {
97 let path = elf_path.as_ref();
98 let data = fs::read(path)
99 .map_err(|e| format!("Failed to read ELF file {}: {}", path.display(), e))?;
100
101 let analysis = analyze_elf_bytes(&data, config)?;
102 Ok(analysis.usage)
103}
104
105pub fn analyze_elf_detailed<P: AsRef<Path>>(elf_path: P, config: &MemoryConfig) -> Result<ElfAnalysis, String> {
107 let path = elf_path.as_ref();
108 let data = fs::read(path)
109 .map_err(|e| format!("Failed to read ELF file {}: {}", path.display(), e))?;
110
111 analyze_elf_bytes(&data, config)
112}
113
114pub fn analyze_elf_bytes(data: &[u8], config: &MemoryConfig) -> Result<ElfAnalysis, String> {
116 if data.len() < 52 {
117 return Err("File too small to be a valid 32-bit ELF".to_string());
118 }
119
120 if &data[0..4] != b"\x7fELF" {
122 return Err("Not a valid ELF file (bad magic)".to_string());
123 }
124
125 let elf_class = data[4];
127 if elf_class != 1 {
128 return Err("Only 32-bit ELF files are supported".to_string());
129 }
130
131 let flash_region = config.flash();
132 let ram_region = config.ram();
133
134 let flash_origin = flash_region.map(|r| r.origin as u32).unwrap_or(0x0800_0000);
135 let flash_length = flash_region.map(|r| r.length).unwrap_or(64 * 1024);
136 let flash_end = flash_origin as u64 + flash_length;
137
138 let ram_origin = ram_region.map(|r| r.origin as u32).unwrap_or(0x2000_0000);
139 let ram_length = ram_region.map(|r| r.length).unwrap_or(20 * 1024);
140 let ram_end = ram_origin as u64 + ram_length;
141
142 let e_shoff = u32::from_le_bytes(data[0x20..0x24].try_into().unwrap()) as usize;
144 let e_shentsize = u16::from_le_bytes(data[0x2E..0x30].try_into().unwrap()) as usize;
145 let e_shnum = u16::from_le_bytes(data[0x30..0x32].try_into().unwrap()) as usize;
146 let e_shstrndx = u16::from_le_bytes(data[0x32..0x34].try_into().unwrap()) as usize;
147
148 if e_shentsize == 0 || e_shnum == 0 {
149 return Ok(ElfAnalysis {
150 usage: FirmwareUsage {
151 flash_used: 0,
152 ram_used: 0,
153 flash_total: flash_length,
154 ram_total: ram_length,
155 },
156 sections: Vec::new(),
157 });
158 }
159
160 let shstrtab = read_section_data(data, e_shoff, e_shstrndx, e_shentsize);
162
163 let mut flash_used: u64 = 0;
164 let mut ram_used: u64 = 0;
165 let mut sections = Vec::new();
166
167 for i in 0..e_shnum {
168 let sh_off = e_shoff + i * e_shentsize;
169 if sh_off + 40 > data.len() {
170 break;
171 }
172
173 let sh_name_idx = u32::from_le_bytes(data[sh_off..sh_off + 4].try_into().unwrap()) as usize;
174 let sh_type = u32::from_le_bytes(data[sh_off + 4..sh_off + 8].try_into().unwrap());
175 let sh_flags = u32::from_le_bytes(data[sh_off + 8..sh_off + 12].try_into().unwrap());
176 let sh_addr = u32::from_le_bytes(data[sh_off + 12..sh_off + 16].try_into().unwrap());
177 let sh_size = u32::from_le_bytes(data[sh_off + 20..sh_off + 24].try_into().unwrap());
178
179 if sh_type == 0 {
181 continue;
182 }
183
184 let name = read_string(&shstrtab, sh_name_idx);
185
186 if sh_flags & 0x2 != 0 {
188 let addr = sh_addr as u64;
189 let region = if addr >= ram_origin as u64 && addr < ram_end {
190 ram_used += sh_size as u64;
191 RegionType::Ram
192 } else if addr >= flash_origin as u64 && addr < flash_end {
193 flash_used += sh_size as u64;
194 RegionType::Flash
195 } else {
196 RegionType::Other
197 };
198
199 sections.push(SectionInfo {
200 name,
201 address: sh_addr,
202 size: sh_size,
203 flags: sh_flags,
204 region,
205 });
206 }
207 }
208
209 sections.sort_by(|a, b| {
211 let ra = match a.region { RegionType::Flash => 0, RegionType::Ram => 1, RegionType::Other => 2 };
212 let rb = match b.region { RegionType::Flash => 0, RegionType::Ram => 1, RegionType::Other => 2 };
213 ra.cmp(&rb).then_with(|| b.size.cmp(&a.size))
214 });
215
216 Ok(ElfAnalysis {
217 usage: FirmwareUsage {
218 flash_used,
219 ram_used,
220 flash_total: flash_length,
221 ram_total: ram_length,
222 },
223 sections,
224 })
225}
226
227fn read_section_data(data: &[u8], e_shoff: usize, index: usize, e_shentsize: usize) -> Vec<u8> {
229 let sh_off = e_shoff + index * e_shentsize;
230 if sh_off + 40 > data.len() {
231 return Vec::new();
232 }
233 let sh_offset = u32::from_le_bytes(data[sh_off + 16..sh_off + 20].try_into().unwrap()) as usize;
234 let sh_size = u32::from_le_bytes(data[sh_off + 20..sh_off + 24].try_into().unwrap()) as usize;
235 if sh_offset + sh_size > data.len() {
236 return Vec::new();
237 }
238 data[sh_offset..sh_offset + sh_size].to_vec()
239}
240
241fn read_string(strtab: &[u8], offset: usize) -> String {
243 if offset >= strtab.len() {
244 return format!("<unknown@{}>", offset);
245 }
246 let end = strtab[offset..]
247 .iter()
248 .position(|&b| b == 0)
249 .unwrap_or(strtab.len() - offset);
250 String::from_utf8_lossy(&strtab[offset..offset + end]).into_owned()
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::memory::MemoryConfig;
257
258 fn test_config() -> MemoryConfig {
259 MemoryConfig::parse(
260 "MEMORY {\n FLASH : ORIGIN = 0x08000000, LENGTH = 64K\n RAM : ORIGIN = 0x20000000, LENGTH = 20K\n}"
261 ).unwrap()
262 }
263
264 #[test]
265 fn test_invalid_data() {
266 let config = test_config();
267 assert!(analyze_elf_bytes(b"too small", &config).is_err());
268 assert!(analyze_elf_bytes(b"NOT_ELF_DATA_HERE_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", &config).is_err());
269 }
270
271 #[test]
272 fn test_analyze_stm32dome() {
273 let config = crate::memory::MemoryConfig::from_file("memory.x").unwrap();
274 let analysis = analyze_elf_detailed("stm32dome", &config).unwrap();
275
276 assert!(analysis.usage.flash_used > 0, "FLASH should have used bytes");
277 assert!(analysis.usage.flash_total > 0);
278 assert!(!analysis.sections.is_empty(), "Should have sections");
279
280 let mut seen_ram = false;
282 for sec in &analysis.sections {
283 if sec.region == RegionType::Ram {
284 seen_ram = true;
285 }
286 if seen_ram && sec.region == RegionType::Flash {
287 panic!("Flash section found after Ram section - sort order broken");
288 }
289 }
290 }
291}