1use object::{
2 Endianness, Object, ObjectSection, elf::FileHeader32, elf::FileHeader64, elf::PT_LOAD,
3 read::elf::ElfFile, read::elf::FileHeader, read::elf::ProgramHeader,
4};
5use probe_rs_target::{InstructionSet, MemoryRange};
6use serde::{Deserialize, Serialize};
7
8use std::{
9 fs::File,
10 path::{Path, PathBuf},
11 str::FromStr,
12};
13
14use super::*;
15use crate::session::Session;
16
17#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
19pub struct BinOptions {
20 pub base_address: Option<u64>,
22 pub skip: u32,
24}
25
26#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
28pub struct IdfOptions {
29 pub bootloader: Option<PathBuf>,
31 pub partition_table: Option<PathBuf>,
33 pub target_app_partition: Option<String>,
35}
36
37#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
39pub struct ElfOptions {
40 pub skip_sections: Vec<String>,
42}
43
44#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
46pub enum FormatKind {
47 Bin,
51 Hex,
53 #[default]
55 Elf,
56 Idf,
59 Uf2,
61}
62
63impl FormatKind {
64 pub fn from_optional(s: Option<&str>) -> Result<Self, String> {
68 match s {
69 Some(format) => Self::from_str(format),
70 None => Ok(Self::default()),
71 }
72 }
73}
74
75impl FromStr for FormatKind {
76 type Err = String;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 match &s.to_lowercase()[..] {
80 "bin" | "binary" => Ok(Self::Bin),
81 "hex" | "ihex" | "intelhex" => Ok(Self::Hex),
82 "elf" => Ok(Self::Elf),
83 "uf2" => Ok(Self::Uf2),
84 "idf" | "esp-idf" | "espidf" => Ok(Self::Idf),
85 _ => Err(format!("Format '{s}' is unknown.")),
86 }
87 }
88}
89
90#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
92pub enum Format {
93 Bin(BinOptions),
97 Hex,
99 Elf(ElfOptions),
101 Idf(IdfOptions),
104 Uf2,
106}
107
108impl Default for Format {
109 fn default() -> Self {
110 Format::Elf(ElfOptions::default())
111 }
112}
113
114impl From<FormatKind> for Format {
115 fn from(kind: FormatKind) -> Self {
116 match kind {
117 FormatKind::Bin => Format::Bin(BinOptions::default()),
118 FormatKind::Hex => Format::Hex,
119 FormatKind::Elf => Format::Elf(ElfOptions::default()),
120 FormatKind::Uf2 => Format::Uf2,
121 FormatKind::Idf => Format::Idf(IdfOptions::default()),
122 }
123 }
124}
125
126#[derive(Debug, thiserror::Error, docsplay::Display)]
131pub enum FileDownloadError {
132 #[ignore_extra_doc_attributes]
134 Flash(#[from] FlashError),
137
138 IhexRead(#[from] ihex::ReaderError),
140
141 IO(#[from] std::io::Error),
143
144 Object(&'static str),
146
147 Elf(#[from] object::read::Error),
149
150 Idf(#[from] espflash::Error),
152
153 IdfUnsupported(String),
155
156 #[ignore_extra_doc_attributes]
158 NoLoadableSegments,
161
162 FlashSizeDetection(#[from] crate::Error),
164
165 IncompatibleImage {
167 target: Vec<InstructionSet>,
169 image: InstructionSet,
171 },
172
173 Other(#[source] crate::Error),
175}
176
177fn print_instr_sets(instr_sets: &[InstructionSet]) -> String {
178 instr_sets
179 .iter()
180 .map(|instr_set| format!("{instr_set:?}"))
181 .collect::<Vec<_>>()
182 .join(", ")
183}
184
185#[derive(Default)]
198#[non_exhaustive]
199pub struct DownloadOptions<'p> {
200 pub progress: FlashProgress<'p>,
202 pub keep_unwritten_bytes: bool,
209 pub dry_run: bool,
211 pub do_chip_erase: bool,
215 pub skip_erase: bool,
218 pub preverify: bool,
220 pub verify: bool,
222 pub disable_double_buffering: bool,
224}
225
226impl DownloadOptions<'_> {
227 pub fn new() -> Self {
229 Self::default()
230 }
231}
232
233pub fn build_loader(
237 session: &mut Session,
238 path: impl AsRef<Path>,
239 format: Format,
240 image_instruction_set: Option<InstructionSet>,
241) -> Result<FlashLoader, FileDownloadError> {
242 let mut loader = session.target().flash_loader();
244
245 let mut file = File::open(path).map_err(FileDownloadError::IO)?;
247
248 loader.load_image(session, &mut file, format, image_instruction_set)?;
249
250 Ok(loader)
251}
252
253pub fn download_file(
259 session: &mut Session,
260 path: impl AsRef<Path>,
261 format: impl Into<Format>,
262) -> Result<(), FileDownloadError> {
263 download_file_with_options(session, path, format, DownloadOptions::default())
264}
265
266pub fn download_file_with_options(
272 session: &mut Session,
273 path: impl AsRef<Path>,
274 format: impl Into<Format>,
275 options: DownloadOptions,
276) -> Result<(), FileDownloadError> {
277 let loader = build_loader(session, path, format.into(), None)?;
278
279 loader
280 .commit(session, options)
281 .map_err(FileDownloadError::Flash)
282}
283
284pub(super) struct ExtractedFlashData<'data> {
286 pub(super) section_names: Vec<String>,
287 pub(super) address: u32,
288 pub(super) data: &'data [u8],
289}
290
291impl std::fmt::Debug for ExtractedFlashData<'_> {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 let mut helper = f.debug_struct("ExtractedFlashData");
294
295 helper
296 .field("name", &self.section_names)
297 .field("address", &self.address);
298
299 if self.data.len() > 10 {
300 helper
301 .field("data", &format!("[..] ({} bytes)", self.data.len()))
302 .finish()
303 } else {
304 helper.field("data", &self.data).finish()
305 }
306 }
307}
308
309fn extract_from_elf_inner<'data, T: FileHeader>(
310 elf_header: &T,
311 binary: ElfFile<'_, T>,
312 elf_data: &'data [u8],
313 options: &ElfOptions,
314) -> Result<Vec<ExtractedFlashData<'data>>, FileDownloadError> {
315 let endian = elf_header.endian()?;
316
317 let mut extracted_data = Vec::new();
318 for segment in elf_header.program_headers(elf_header.endian()?, elf_data)? {
319 let p_paddr: u64 = segment.p_paddr(endian).into();
321
322 let p_vaddr: u64 = segment.p_vaddr(endian).into();
323
324 let flags = segment.p_flags(endian);
325
326 let segment_data = segment
327 .data(endian, elf_data)
328 .map_err(|_| FileDownloadError::Object("Failed to access data for an ELF segment."))?;
329
330 let mut elf_section = Vec::new();
331
332 if !segment_data.is_empty() && segment.p_type(endian) == PT_LOAD {
333 tracing::info!(
334 "Found loadable segment, physical address: {:#010x}, virtual address: {:#010x}, flags: {:#x}",
335 p_paddr,
336 p_vaddr,
337 flags
338 );
339
340 let (segment_offset, segment_filesize) = segment.file_range(endian);
341
342 let sector = segment_offset..segment_offset + segment_filesize;
343
344 for section in binary.sections() {
345 let (section_offset, section_filesize) = match section.file_range() {
346 Some(range) => range,
347 None => continue,
348 };
349
350 if sector.contains_range(&(section_offset..section_offset + section_filesize)) {
351 let name = section.name()?;
352 if options.skip_sections.iter().any(|skip| skip == name) {
353 tracing::info!("Skipping section: {:?}", name);
354 continue;
355 }
356 tracing::info!("Matching section: {:?}", name);
357
358 #[cfg(feature = "hexdump")]
359 for line in hexdump::hexdump_iter(section.data()?) {
360 tracing::trace!("{}", line);
361 }
362
363 for (offset, relocation) in section.relocations() {
364 tracing::info!(
365 "Relocation: offset={}, relocation={:?}",
366 offset,
367 relocation
368 );
369 }
370
371 elf_section.push(name.to_owned());
372 }
373 }
374
375 if elf_section.is_empty() {
376 tracing::info!("Not adding segment, no matching sections found.");
377 } else {
378 let section_data =
379 &elf_data[segment_offset as usize..][..segment_filesize as usize];
380
381 extracted_data.push(ExtractedFlashData {
382 section_names: elf_section,
383 address: p_paddr as u32,
384 data: section_data,
385 });
386 }
387 }
388 }
389
390 Ok(extracted_data)
391}
392
393pub(super) fn extract_from_elf<'a>(
394 elf_data: &'a [u8],
395 options: &ElfOptions,
396) -> Result<Vec<ExtractedFlashData<'a>>, FileDownloadError> {
397 let file_kind = object::FileKind::parse(elf_data)?;
398
399 match file_kind {
400 object::FileKind::Elf32 => {
401 let elf_header = FileHeader32::<Endianness>::parse(elf_data)?;
402 let binary = object::read::elf::ElfFile::<FileHeader32<Endianness>>::parse(elf_data)?;
403 extract_from_elf_inner(elf_header, binary, elf_data, options)
404 }
405 object::FileKind::Elf64 => {
406 let elf_header = FileHeader64::<Endianness>::parse(elf_data)?;
407 let binary = object::read::elf::ElfFile::<FileHeader64<Endianness>>::parse(elf_data)?;
408 extract_from_elf_inner(elf_header, binary, elf_data, options)
409 }
410 _ => Err(FileDownloadError::Object("Unsupported file type")),
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use std::str::FromStr;
417
418 use super::FormatKind;
419
420 #[test]
421 fn parse_format() {
422 assert_eq!(FormatKind::from_str("hex"), Ok(FormatKind::Hex));
423 assert_eq!(FormatKind::from_str("Hex"), Ok(FormatKind::Hex));
424 assert_eq!(FormatKind::from_str("Ihex"), Ok(FormatKind::Hex));
425 assert_eq!(FormatKind::from_str("IHex"), Ok(FormatKind::Hex));
426 assert_eq!(FormatKind::from_str("iHex"), Ok(FormatKind::Hex));
427 assert_eq!(FormatKind::from_str("IntelHex"), Ok(FormatKind::Hex));
428 assert_eq!(FormatKind::from_str("intelhex"), Ok(FormatKind::Hex));
429 assert_eq!(FormatKind::from_str("intelHex"), Ok(FormatKind::Hex));
430 assert_eq!(FormatKind::from_str("Intelhex"), Ok(FormatKind::Hex));
431 assert_eq!(FormatKind::from_str("bin"), Ok(FormatKind::Bin));
432 assert_eq!(FormatKind::from_str("Bin"), Ok(FormatKind::Bin));
433 assert_eq!(FormatKind::from_str("binary"), Ok(FormatKind::Bin));
434 assert_eq!(FormatKind::from_str("Binary"), Ok(FormatKind::Bin));
435 assert_eq!(FormatKind::from_str("Elf"), Ok(FormatKind::Elf));
436 assert_eq!(FormatKind::from_str("elf"), Ok(FormatKind::Elf));
437 assert_eq!(FormatKind::from_str("idf"), Ok(FormatKind::Idf));
438 assert_eq!(FormatKind::from_str("espidf"), Ok(FormatKind::Idf));
439 assert_eq!(FormatKind::from_str("esp-idf"), Ok(FormatKind::Idf));
440 assert_eq!(FormatKind::from_str("ESP-IDF"), Ok(FormatKind::Idf));
441 assert_eq!(
442 FormatKind::from_str("elfbin"),
443 Err("Format 'elfbin' is unknown.".to_string())
444 );
445 assert_eq!(
446 FormatKind::from_str(""),
447 Err("Format '' is unknown.".to_string())
448 );
449 assert_eq!(
450 FormatKind::from_str("asdasdf"),
451 Err("Format 'asdasdf' is unknown.".to_string())
452 );
453 }
454}