From 421a18445fce1f26aa11281e747237ca7927c0d3 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Mon, 15 Dec 2025 23:52:46 +0000 Subject: [PATCH 01/17] firmware_file: Refactored out the FirmwareFormat enum into a new file --- src/bmp.rs | 30 ------------------------------ src/firmware_file.rs | 33 +++++++++++++++++++++++++++++++++ src/flasher.rs | 3 ++- src/lib.rs | 1 + 4 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/firmware_file.rs diff --git a/src/bmp.rs b/src/bmp.rs index 0b6e742..d47a719 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -759,36 +759,6 @@ impl ValueEnum for FirmwareType } } -/// File formats that Black Magic Probe firmware can be in. -pub enum FirmwareFormat -{ - /// Raw binary format. Made with `objcopy -O binary`. Typical file extension: `.bin`. - Binary, - - /// The Unix ELF executable binary format. Typical file extension: `.elf`. - Elf, - - /// Intel HEX. Typical file extensions: `.hex`, `.ihex`. - IntelHex, -} - -impl FirmwareFormat -{ - /// Detect the kind of firmware from its data. - /// - /// Panics if `firmware.len() < 4`. - pub fn detect_from_firmware(firmware: &[u8]) -> Self - { - if &firmware[0..4] == b"\x7fELF" { - FirmwareFormat::Elf - } else if &firmware[0..1] == b":" { - FirmwareFormat::IntelHex - } else { - FirmwareFormat::Binary - } - } -} - /// Waits for a Black Magic Probe to reboot, erroring after a timeout. /// /// This function takes a port identifier to attempt to keep track of a single physical device diff --git a/src/firmware_file.rs b/src/firmware_file.rs new file mode 100644 index 0000000..5061243 --- /dev/null +++ b/src/firmware_file.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +/// File formats that Black Magic Probe firmware can be in. +pub enum FirmwareFormat +{ + /// Raw binary format. Made with `objcopy -O binary`. Typical file extension: `.bin`. + Binary, + + /// The Unix ELF executable binary format. Typical file extension: `.elf`. + Elf, + + /// Intel HEX. Typical file extensions: `.hex`, `.ihex`. + IntelHex, +} + +impl FirmwareFormat +{ + /// Detect the kind of firmware from its data. + /// + /// Panics if `firmware.len() < 4`. + pub fn detect_from_firmware(firmware: &[u8]) -> Self + { + if &firmware[0..4] == b"\x7fELF" { + FirmwareFormat::Elf + } else if &firmware[0..1] == b":" { + FirmwareFormat::IntelHex + } else { + FirmwareFormat::Binary + } + } +} diff --git a/src/flasher.rs b/src/flasher.rs index edf34dc..2e110ae 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -14,7 +14,8 @@ use color_eyre::owo_colors::OwoColorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, warn}; -use crate::bmp::{self, BmpDevice, FirmwareFormat, FirmwareType}; +use crate::bmp::{self, BmpDevice, FirmwareType}; +use crate::firmware_file::FirmwareFormat; use crate::usb::PortId; use crate::{AllowDangerous, BmpParams, FlashParams, elf}; diff --git a/src/lib.rs b/src/lib.rs index d55b2b5..a450f00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod bmp_matcher; pub mod docs_viewer; pub mod elf; pub mod error; +pub mod firmware_file; pub mod firmware_selector; pub mod flasher; pub mod metadata; From e7eaa3769512d658347ad7b2299c2e6dc9a451ea Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 06:00:41 +0000 Subject: [PATCH 02/17] firmware_file: Implemented logic for figuring out what kind of file we're working with and dispatching to a file-specific loader --- src/firmware_file.rs | 119 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/firmware_file.rs b/src/firmware_file.rs index 5061243..86da498 100644 --- a/src/firmware_file.rs +++ b/src/firmware_file.rs @@ -2,6 +2,12 @@ // SPDX-FileCopyrightText: 2025 1BitSquared // SPDX-FileContributor: Written by Rachel Mant +use std::fs::File; +use std::io::{Read, Seek}; +use std::path::Path; + +use color_eyre::eyre::{Context, Result, eyre}; + /// File formats that Black Magic Probe firmware can be in. pub enum FirmwareFormat { @@ -15,6 +21,119 @@ pub enum FirmwareFormat IntelHex, } +trait FirmwareStorage +{ + fn find_load_address(&self) -> Option; + fn firmware_data(&self) -> &[u8]; +} + +struct RawFirmwareFile +{ + contents: Vec, +} + +struct IntelHexFirmwareFile {} + +struct ELFFirmwareFile {} + +pub struct FirmwareFile +{ + inner: Box, +} + +impl FirmwareFile +{ + /// Construct a FirmwareFile from a path to a file + pub fn from_path(file_name: &Path) -> Result + { + let mut file = + File::open(file_name).wrap_err_with(|| eyre!("Failed to read file {} as firmware", file_name.display()))?; + + let mut signature = [0u8; 4]; + let _ = file.read(&mut signature)?; + file.rewind()?; + + let storage: Box = if &signature == b"\x7fELF" { + Box::new(ELFFirmwareFile::from(file)) + } else if &signature[0..1] == b":" { + Box::new(IntelHexFirmwareFile::from(file)) + } else { + Box::new(RawFirmwareFile::from(file)?) + }; + + Ok(Self { + inner: storage, + }) + } +} + +impl RawFirmwareFile +{ + fn from(mut file: File) -> Result + { + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + Ok(Self { + contents, + }) + } +} + +impl FirmwareStorage for RawFirmwareFile +{ + fn find_load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &self.contents + } +} + +impl IntelHexFirmwareFile +{ + fn from(_file: File) -> Self + { + Self {} + } +} + +impl FirmwareStorage for IntelHexFirmwareFile +{ + fn find_load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &[] + } +} + +impl ELFFirmwareFile +{ + fn from(_file: File) -> Self + { + Self {} + } +} + +impl FirmwareStorage for ELFFirmwareFile +{ + fn find_load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &[] + } +} + impl FirmwareFormat { /// Detect the kind of firmware from its data. From e1d6bc5d40874b880956bc23e9317e4238014ab1 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 06:16:18 +0000 Subject: [PATCH 03/17] firmware_file: Reorganised the firmware loading logic into a module w/ modules for each of the file types --- src/firmware_file.rs | 152 -------------------------------------- src/firmware_file/elf.rs | 30 ++++++++ src/firmware_file/ihex.rs | 38 ++++++++++ src/firmware_file/mod.rs | 64 ++++++++++++++++ src/firmware_file/raw.rs | 42 +++++++++++ src/flasher.rs | 54 +++----------- 6 files changed, 183 insertions(+), 197 deletions(-) delete mode 100644 src/firmware_file.rs create mode 100644 src/firmware_file/elf.rs create mode 100644 src/firmware_file/ihex.rs create mode 100644 src/firmware_file/mod.rs create mode 100644 src/firmware_file/raw.rs diff --git a/src/firmware_file.rs b/src/firmware_file.rs deleted file mode 100644 index 86da498..0000000 --- a/src/firmware_file.rs +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// SPDX-FileCopyrightText: 2025 1BitSquared -// SPDX-FileContributor: Written by Rachel Mant - -use std::fs::File; -use std::io::{Read, Seek}; -use std::path::Path; - -use color_eyre::eyre::{Context, Result, eyre}; - -/// File formats that Black Magic Probe firmware can be in. -pub enum FirmwareFormat -{ - /// Raw binary format. Made with `objcopy -O binary`. Typical file extension: `.bin`. - Binary, - - /// The Unix ELF executable binary format. Typical file extension: `.elf`. - Elf, - - /// Intel HEX. Typical file extensions: `.hex`, `.ihex`. - IntelHex, -} - -trait FirmwareStorage -{ - fn find_load_address(&self) -> Option; - fn firmware_data(&self) -> &[u8]; -} - -struct RawFirmwareFile -{ - contents: Vec, -} - -struct IntelHexFirmwareFile {} - -struct ELFFirmwareFile {} - -pub struct FirmwareFile -{ - inner: Box, -} - -impl FirmwareFile -{ - /// Construct a FirmwareFile from a path to a file - pub fn from_path(file_name: &Path) -> Result - { - let mut file = - File::open(file_name).wrap_err_with(|| eyre!("Failed to read file {} as firmware", file_name.display()))?; - - let mut signature = [0u8; 4]; - let _ = file.read(&mut signature)?; - file.rewind()?; - - let storage: Box = if &signature == b"\x7fELF" { - Box::new(ELFFirmwareFile::from(file)) - } else if &signature[0..1] == b":" { - Box::new(IntelHexFirmwareFile::from(file)) - } else { - Box::new(RawFirmwareFile::from(file)?) - }; - - Ok(Self { - inner: storage, - }) - } -} - -impl RawFirmwareFile -{ - fn from(mut file: File) -> Result - { - let mut contents = Vec::new(); - file.read_to_end(&mut contents)?; - Ok(Self { - contents, - }) - } -} - -impl FirmwareStorage for RawFirmwareFile -{ - fn find_load_address(&self) -> Option - { - None - } - - fn firmware_data(&self) -> &[u8] - { - &self.contents - } -} - -impl IntelHexFirmwareFile -{ - fn from(_file: File) -> Self - { - Self {} - } -} - -impl FirmwareStorage for IntelHexFirmwareFile -{ - fn find_load_address(&self) -> Option - { - None - } - - fn firmware_data(&self) -> &[u8] - { - &[] - } -} - -impl ELFFirmwareFile -{ - fn from(_file: File) -> Self - { - Self {} - } -} - -impl FirmwareStorage for ELFFirmwareFile -{ - fn find_load_address(&self) -> Option - { - None - } - - fn firmware_data(&self) -> &[u8] - { - &[] - } -} - -impl FirmwareFormat -{ - /// Detect the kind of firmware from its data. - /// - /// Panics if `firmware.len() < 4`. - pub fn detect_from_firmware(firmware: &[u8]) -> Self - { - if &firmware[0..4] == b"\x7fELF" { - FirmwareFormat::Elf - } else if &firmware[0..1] == b":" { - FirmwareFormat::IntelHex - } else { - FirmwareFormat::Binary - } - } -} diff --git a/src/firmware_file/elf.rs b/src/firmware_file/elf.rs new file mode 100644 index 0000000..cc3e208 --- /dev/null +++ b/src/firmware_file/elf.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; + +use super::FirmwareStorage; + +pub struct ELFFirmwareFile {} + +impl From for ELFFirmwareFile +{ + fn from(_file: File) -> Self + { + Self {} + } +} + +impl FirmwareStorage for ELFFirmwareFile +{ + fn load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &[] + } +} diff --git a/src/firmware_file/ihex.rs b/src/firmware_file/ihex.rs new file mode 100644 index 0000000..74779cb --- /dev/null +++ b/src/firmware_file/ihex.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; + +use owo_colors::OwoColorize; + +use super::FirmwareStorage; + +pub struct IntelHexFirmwareFile {} + +impl From for IntelHexFirmwareFile +{ + fn from(_file: File) -> Self + { + eprintln!( + "{} The specified firmware file appears to be an Intel HEX file, but Intel HEX files are not currently \ + supported. Please use a binary file (e.g. blackmagic.bin), or an ELF (e.g. blackmagic.elf) to flash.", + "Error:".red() + ); + std::process::exit(1); + // Self {} + } +} + +impl FirmwareStorage for IntelHexFirmwareFile +{ + fn load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &[] + } +} diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs new file mode 100644 index 0000000..c0599d7 --- /dev/null +++ b/src/firmware_file/mod.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; +use std::io::{Read, Seek}; +use std::path::Path; + +use color_eyre::eyre::{Context, Result, eyre}; + +mod elf; +mod ihex; +mod raw; + +use self::elf::ELFFirmwareFile; +use self::ihex::IntelHexFirmwareFile; +use self::raw::RawFirmwareFile; + +trait FirmwareStorage +{ + fn load_address(&self) -> Option; + fn firmware_data(&self) -> &[u8]; +} + +pub struct FirmwareFile +{ + inner: Box, +} + +impl FirmwareFile +{ + /// Construct a FirmwareFile from a path to a file + pub fn from_path(file_name: &Path) -> Result + { + let mut file = + File::open(file_name).wrap_err_with(|| eyre!("Failed to read file {} as firmware", file_name.display()))?; + + let mut signature = [0u8; 4]; + let _ = file.read(&mut signature)?; + file.rewind()?; + + let storage: Box = if &signature == b"\x7fELF" { + Box::new(ELFFirmwareFile::from(file)) + } else if &signature[0..1] == b":" { + Box::new(IntelHexFirmwareFile::from(file)) + } else { + Box::new(RawFirmwareFile::try_from(file)?) + }; + + Ok(Self { + inner: storage, + }) + } + + pub fn load_address(&self) -> Option + { + self.inner.load_address() + } + + pub fn firmware_data(&self) -> &[u8] + { + self.inner.firmware_data() + } +} diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs new file mode 100644 index 0000000..586cfa6 --- /dev/null +++ b/src/firmware_file/raw.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; +use std::io::Read; + +use color_eyre::eyre::{Report, Result}; + +use super::FirmwareStorage; + +pub struct RawFirmwareFile +{ + contents: Vec, +} + +impl TryFrom for RawFirmwareFile +{ + type Error = Report; + + fn try_from(mut file: File) -> Result + { + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + Ok(Self { + contents, + }) + } +} + +impl FirmwareStorage for RawFirmwareFile +{ + fn load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &self.contents + } +} diff --git a/src/flasher.rs b/src/flasher.rs index 2e110ae..17e7737 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -3,7 +3,7 @@ // SPDX-FileContributor: Written by Mikaela Szekely // SPDX-FileContributor: Modified by Rachel Mant -use std::io::{Read, Write}; +use std::io::Write; use std::path::PathBuf; use std::rc::Rc; use std::thread; @@ -15,9 +15,9 @@ use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, warn}; use crate::bmp::{self, BmpDevice, FirmwareType}; -use crate::firmware_file::FirmwareFormat; +use crate::firmware_file::FirmwareFile; use crate::usb::PortId; -use crate::{AllowDangerous, BmpParams, FlashParams, elf}; +use crate::{AllowDangerous, BmpParams, FlashParams}; pub struct Firmware { @@ -28,17 +28,18 @@ pub struct Firmware impl Firmware { - pub fn new(params: &Params, device: &BmpDevice, firmware_data: Vec) -> Result + pub fn new(params: &Params, device: &BmpDevice, firmware_file: FirmwareFile) -> Result where Params: BmpParams + FlashParams, { + let firmware_data = firmware_file.firmware_data(); let firmware_length = firmware_data.len(); let firmware_length = u32::try_from(firmware_length) .expect("firmware filesize exceeded 32 bits! Firmware binary must be invalid (too big)"); Ok(Self { - firmware_type: Self::determine_firmware_type(params, device, &firmware_data)?, - data: firmware_data, + firmware_type: Self::determine_firmware_type(params, device, firmware_data)?, + data: firmware_data.to_vec(), length: firmware_length, }) } @@ -128,43 +129,6 @@ impl Firmware } } -fn intel_hex_error() -> ! -{ - eprintln!( - "{} The specified firmware file appears to be an Intel HEX file, but Intel HEX files are not currently \ - supported. Please use a binary file (e.g. blackmagic.bin), or an ELF (e.g. blackmagic.elf) to flash.", - "Error:".red() - ); - std::process::exit(1); -} - -fn read_firmware(file_name: PathBuf) -> Result> -{ - let firmware_file = std::fs::File::open(file_name.as_path()) - .wrap_err_with(|| eyre!("Failed to read firmware file {} to Flash", file_name.display()))?; - - let mut firmware_file = std::io::BufReader::new(firmware_file); - - let mut firmware_data = Vec::new(); - firmware_file.read_to_end(&mut firmware_data).unwrap(); - - // FirmwareFormat::detect_from_firmware() needs at least 4 bytes, and - // FirmwareType::detect_from_firmware() needs at least 8 bytes, - // but also if we don't even have 8 bytes there's _no way_ this is valid firmware. - if firmware_data.len() < 8 { - return Err(eyre!("Firmware file appears invalid: less than 8 bytes long")); - } - - // Extract the actual firmware data from the file, based on the format we're using. - let firmware_data = match FirmwareFormat::detect_from_firmware(&firmware_data) { - FirmwareFormat::Binary => firmware_data, - FirmwareFormat::Elf => elf::extract_binary(&firmware_data)?, - FirmwareFormat::IntelHex => intel_hex_error(), // FIXME: implement this. - }; - - Ok(firmware_data) -} - fn check_programming(port: PortId) -> Result<()> { let dev = bmp::wait_for_probe_reboot(port, Duration::from_secs(5), "flash").inspect_err(|_| { @@ -189,12 +153,12 @@ pub fn flash_probe(params: &Params, mut device: BmpDevice, file_name: Pa where Params: BmpParams + FlashParams, { - let firmware_data = read_firmware(file_name)?; + let firmware_file = FirmwareFile::from_path(&file_name)?; // Grab the the port the probe can be found on, which we need to re-find the probe after rebooting. let port = device.port(); - let firmware = Firmware::new(params, &device, firmware_data)?; + let firmware = Firmware::new(params, &device, firmware_file)?; // If we can't get the string descriptors, try to go ahead with flashing anyway. // It's unlikely that other control requests will succeed, but the OS might be messing with From 2a52aa755002f798c3c80acecaa3430762f818e9 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 09:06:32 +0000 Subject: [PATCH 04/17] firmware_file: Added some documentation that was missing --- src/firmware_file/mod.rs | 2 ++ src/firmware_file/raw.rs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs index c0599d7..fa40a1f 100644 --- a/src/firmware_file/mod.rs +++ b/src/firmware_file/mod.rs @@ -57,6 +57,8 @@ impl FirmwareFile self.inner.load_address() } + /// Provides the firmware data this file holds in a format suitable for + /// writing into Flash directly at the load address pub fn firmware_data(&self) -> &[u8] { self.inner.firmware_data() diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs index 586cfa6..0aac5cb 100644 --- a/src/firmware_file/raw.rs +++ b/src/firmware_file/raw.rs @@ -20,8 +20,11 @@ impl TryFrom for RawFirmwareFile fn try_from(mut file: File) -> Result { + // Pull out the entire file contents into memory and stuff it in a vec let mut contents = Vec::new(); file.read_to_end(&mut contents)?; + + // Put the vec inside our little container and be done Ok(Self { contents, }) From de180b8ebff9bfec15c28016b54ca82f5af1a638 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 13:00:18 -0800 Subject: [PATCH 05/17] firmware_file/raw: Optimise slightly by holding onto a `Box<[u8]>` instead of a `Vec` for the firmware data --- src/firmware_file/raw.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs index 0aac5cb..055c19b 100644 --- a/src/firmware_file/raw.rs +++ b/src/firmware_file/raw.rs @@ -11,7 +11,7 @@ use super::FirmwareStorage; pub struct RawFirmwareFile { - contents: Vec, + contents: Box<[u8]>, } impl TryFrom for RawFirmwareFile @@ -26,7 +26,7 @@ impl TryFrom for RawFirmwareFile // Put the vec inside our little container and be done Ok(Self { - contents, + contents: contents.into_boxed_slice(), }) } } From 583fd358c569ff8f64acd24d08a1ba749bae4175 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 21:36:32 -0800 Subject: [PATCH 06/17] firmware_file/elf: Validate and parse the ELF data and extract the segments we care about for loading firmware --- src/firmware_file/elf.rs | 51 ++++++++++++++++++++++++++++++++++++---- src/firmware_file/mod.rs | 2 +- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/firmware_file/elf.rs b/src/firmware_file/elf.rs index cc3e208..7319bf9 100644 --- a/src/firmware_file/elf.rs +++ b/src/firmware_file/elf.rs @@ -2,17 +2,58 @@ // SPDX-FileCopyrightText: 2025 1BitSquared // SPDX-FileContributor: Written by Rachel Mant -use std::fs::File; +use std::ops::Range; +use std::{collections::BTreeMap, fs::File, io::Read}; + +use color_eyre::eyre::{Report, Result, eyre}; +use goblin::container::Endian; +use goblin::elf::program_header::PT_LOAD; +use goblin::elf::{Elf, header::{EI_CLASS, ELFCLASS32, EM_ARM, ET_EXEC}}; use super::FirmwareStorage; -pub struct ELFFirmwareFile {} +pub struct ELFFirmwareFile +{ + contents: Box<[u8]>, + segments: BTreeMap> +} -impl From for ELFFirmwareFile +impl TryFrom for ELFFirmwareFile { - fn from(_file: File) -> Self + type Error = Report; + + fn try_from(mut file: File) -> Result { - Self {} + // Extract the contents of the ELF file into memory + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + let contents = contents.into_boxed_slice(); + + // Try to parse the file as an ELF + let elf = Elf::parse(&contents)?; + + // Validate the header is for a 32-bit ARM device + let header = elf.header; + if header.e_type != ET_EXEC || header.e_machine != EM_ARM || + header.endianness()? != Endian::Little || header.e_ident[EI_CLASS] != ELFCLASS32 { + return Err(eyre!("ELF does not represent firmware for a Black Magic Debug device")); + } + + // Extract loadable non-zero-length program headers + let segments = elf.program_headers + .iter() + .flat_map(|header| + { + // Map into base address + file byte range for the data covered by the segment + (header.p_type == PT_LOAD && header.p_filesz != 0) + .then_some((header.p_paddr as u32, header.file_range())) + }) + .collect::>(); + + Ok(Self { + contents, + segments, + }) } } diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs index fa40a1f..95467ac 100644 --- a/src/firmware_file/mod.rs +++ b/src/firmware_file/mod.rs @@ -40,7 +40,7 @@ impl FirmwareFile file.rewind()?; let storage: Box = if &signature == b"\x7fELF" { - Box::new(ELFFirmwareFile::from(file)) + Box::new(ELFFirmwareFile::try_from(file)?) } else if &signature[0..1] == b":" { Box::new(IntelHexFirmwareFile::from(file)) } else { From 3947b27081f777af9c6f1a83016ff3eddcfcf545 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 22:28:49 -0800 Subject: [PATCH 07/17] firmware_file/elf: Implemented building a finalised firmware image from an ELF and completed the FirmwareStorage interface for ELFFirmwareFile --- src/firmware_file/elf.rs | 73 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/src/firmware_file/elf.rs b/src/firmware_file/elf.rs index 7319bf9..c27dec9 100644 --- a/src/firmware_file/elf.rs +++ b/src/firmware_file/elf.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2025 1BitSquared // SPDX-FileContributor: Written by Rachel Mant +use std::iter::zip; use std::ops::Range; use std::{collections::BTreeMap, fs::File, io::Read}; @@ -15,7 +16,8 @@ use super::FirmwareStorage; pub struct ELFFirmwareFile { contents: Box<[u8]>, - segments: BTreeMap> + segments: BTreeMap>, + firmware_image: Box<[u8]>, } impl TryFrom for ELFFirmwareFile @@ -42,18 +44,74 @@ impl TryFrom for ELFFirmwareFile // Extract loadable non-zero-length program headers let segments = elf.program_headers .iter() - .flat_map(|header| - { + .flat_map(|header| { // Map into base address + file byte range for the data covered by the segment (header.p_type == PT_LOAD && header.p_filesz != 0) .then_some((header.p_paddr as u32, header.file_range())) }) .collect::>(); - Ok(Self { + // Make one of ourself + let mut result = Self { contents, segments, - }) + firmware_image: Box::default(), + }; + // Use the data to make the firmware image + result.build_firmware_image(); + + Ok(result) + } +} + +impl ELFFirmwareFile +{ + fn build_firmware_image(&mut self) + { + // Figure out where the first segment sits + let load_address = self.load_address().unwrap_or(0); + + // Extract slices for each of the segments ready to flatten out + let segments_data = self.segments + .values() + .map(|range| { + &self.contents[range.clone()] + }) + .collect::>(); + + // Extract a set of position and length ranges + let segment_ranges = self.segments + .iter() + .map(|(&address, range)| { + address..address + (range.len() as u32) + }) + .collect::>(); + + // Figure out the total length of the flattened firmware image to allocate + let total_length = segment_ranges + .clone() + .into_iter() + .reduce(|a, b| { + a.start..b.end + }) + .map(|range| range.len()) + .unwrap_or(0); + + // Allocate enough memory to hold the completely flattened firmware image + // and initialise it to the erased byte value + let mut firmware_image = vec![0xffu8; total_length].into_boxed_slice(); + + // Loop through all the segments of data and copy them to their final resting places + // in the flattened image + for (segment, position) in zip(segments_data, segment_ranges) { + // Calculate the location of the segment in the flattened image + let range = ((position.start - load_address) as usize)..((position.end - load_address) as usize); + // Grab the relevant slice and copy the segment data in + firmware_image[range].copy_from_slice(segment); + } + + // Store the resulting image back on ourself + self.firmware_image = firmware_image; } } @@ -61,11 +119,12 @@ impl FirmwareStorage for ELFFirmwareFile { fn load_address(&self) -> Option { - None + // Extract the first segment (the map ordered them for us) and return its address + self.segments.first_key_value().map(|(&address, _)| address) } fn firmware_data(&self) -> &[u8] { - &[] + &self.firmware_image } } From 72afe6733b1081f43371189f11b0f52f49a9f04c Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 22:52:24 -0800 Subject: [PATCH 08/17] firmware_file: Added some debug prints to help with understanding and debugging execution flow --- src/firmware_file/elf.rs | 5 +++++ src/firmware_file/mod.rs | 3 +++ src/firmware_file/raw.rs | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/firmware_file/elf.rs b/src/firmware_file/elf.rs index c27dec9..9dc16bf 100644 --- a/src/firmware_file/elf.rs +++ b/src/firmware_file/elf.rs @@ -10,6 +10,7 @@ use color_eyre::eyre::{Report, Result, eyre}; use goblin::container::Endian; use goblin::elf::program_header::PT_LOAD; use goblin::elf::{Elf, header::{EI_CLASS, ELFCLASS32, EM_ARM, ET_EXEC}}; +use log::debug; use super::FirmwareStorage; @@ -26,6 +27,7 @@ impl TryFrom for ELFFirmwareFile fn try_from(mut file: File) -> Result { + debug!("Loading file as ELF firmware binary"); // Extract the contents of the ELF file into memory let mut contents = Vec::new(); file.read_to_end(&mut contents)?; @@ -50,6 +52,7 @@ impl TryFrom for ELFFirmwareFile .then_some((header.p_paddr as u32, header.file_range())) }) .collect::>(); + debug!("Consuming {} segments from file", segments.len()); // Make one of ourself let mut result = Self { @@ -70,6 +73,7 @@ impl ELFFirmwareFile { // Figure out where the first segment sits let load_address = self.load_address().unwrap_or(0); + debug!("Firmware image loads at 0x{load_address:08x}"); // Extract slices for each of the segments ready to flatten out let segments_data = self.segments @@ -96,6 +100,7 @@ impl ELFFirmwareFile }) .map(|range| range.len()) .unwrap_or(0); + debug!("Firmware is {total_length} bytes long flattened"); // Allocate enough memory to hold the completely flattened firmware image // and initialise it to the erased byte value diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs index 95467ac..b0f04fe 100644 --- a/src/firmware_file/mod.rs +++ b/src/firmware_file/mod.rs @@ -7,6 +7,7 @@ use std::io::{Read, Seek}; use std::path::Path; use color_eyre::eyre::{Context, Result, eyre}; +use log::debug; mod elf; mod ihex; @@ -35,6 +36,8 @@ impl FirmwareFile let mut file = File::open(file_name).wrap_err_with(|| eyre!("Failed to read file {} as firmware", file_name.display()))?; + debug!("Reading firmware from {}", file_name.display()); + let mut signature = [0u8; 4]; let _ = file.read(&mut signature)?; file.rewind()?; diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs index 055c19b..1032bce 100644 --- a/src/firmware_file/raw.rs +++ b/src/firmware_file/raw.rs @@ -6,6 +6,7 @@ use std::fs::File; use std::io::Read; use color_eyre::eyre::{Report, Result}; +use log::debug; use super::FirmwareStorage; @@ -20,6 +21,7 @@ impl TryFrom for RawFirmwareFile fn try_from(mut file: File) -> Result { + debug!("Loading file as raw firmware binary"); // Pull out the entire file contents into memory and stuff it in a vec let mut contents = Vec::new(); file.read_to_end(&mut contents)?; From f064d105baf834a61f4a2e11ed7fec509b89718f Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 22:57:43 -0800 Subject: [PATCH 09/17] bmputil: Removed the now unnecessary ELF module in the root of the crate --- src/elf.rs | 98 ------------------------------------------------------ src/lib.rs | 1 - 2 files changed, 99 deletions(-) delete mode 100644 src/elf.rs diff --git a/src/elf.rs b/src/elf.rs deleted file mode 100644 index ef6d4fa..0000000 --- a/src/elf.rs +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// SPDX-FileCopyrightText: 2022-2025 1BitSquared -// SPDX-FileContributor: Written by Mikaela Szekely -// SPDX-FileContributor: Modified by Rachel Mant - -use color_eyre::eyre::Result; -use goblin::elf::{Elf, SectionHeader}; -use goblin::error::Error as GoblinError; - -/// Convenience extensions to [Elf]. -trait ElfExt -{ - /// Get a reference to a section header with the given name. Returns None if - /// a section by that name does not exist. - fn get_section_by_name(&self, name: &str) -> Option<&goblin::elf::SectionHeader>; -} - -impl<'a> ElfExt for Elf<'a> -{ - fn get_section_by_name(&self, name: &str) -> Option<&goblin::elf::SectionHeader> - { - for section in &self.section_headers { - let parsed_name = self.shdr_strtab.get_at(section.sh_name)?; - - if parsed_name == name { - return Some(section); - } - } - - None - } -} - -/// Convenience extensions to [SectionHeader]. -trait SectionHeaderExt -{ - /// Get the raw data of this section, given the full ELF data. - fn get_data<'s>(&'s self, parent_data: &'s [u8]) -> Result<&'s [u8]>; -} - -impl SectionHeaderExt for SectionHeader -{ - fn get_data<'s>(&'s self, parent_data: &'s [u8]) -> Result<&'s [u8]> - { - let start_idx = self.sh_offset as usize; - let size = self.sh_size; - let end_idx = start_idx + size as usize; - let data: &[u8] = parent_data.get(start_idx..end_idx).ok_or_else(|| { - GoblinError::Malformed(format!( - "ELF section header does not point to a valid section (offset [{}..{}])", - start_idx, end_idx, - )) - })?; - - Ok(data) - } -} - -/// Extracts binary data from raw ELF data. -/// -/// This should be equivalent to `$ arm-none-eabi-objcopy -Obinary`, but is not yet robust -/// enough to automatically detect what sections should be copied. -/// Currently, `.text`, `.ARM.exidx`, and `.data` are copied. -pub fn extract_binary(elf_data: &[u8]) -> Result> -{ - let elf = Elf::parse(elf_data)?; - - // FIXME: Dynamically detect what sections should be copied. - // arm-none-eabi-objcopy seems to only copy these three, but I'm not yet certain why only these three - // (as these aren't the only three that have PROGBITS set). - - let text = elf - .get_section_by_name(".text") - .ok_or_else(|| GoblinError::Malformed("ELF .text section not found".into()))? - .get_data(elf_data)?; - - // Allow .ARM.exidx to not exist. - let arm_exidx = elf - .get_section_by_name(".ARM.exidx") - .and_then(|v| v.get_data(elf_data).ok()); - let arm_exidx_len = arm_exidx.map(|sect| sect.len()).unwrap_or(0); - - let data = elf - .get_section_by_name(".data") - .ok_or_else(|| GoblinError::Malformed("ELF .data section not found".into()))? - .get_data(elf_data)?; - - let mut extracted = Vec::with_capacity(text.len() + arm_exidx_len + data.len()); - - extracted.extend_from_slice(text); - if let Some(arm_exidx) = arm_exidx { - extracted.extend_from_slice(arm_exidx); - } - - extracted.extend_from_slice(data); - - Ok(extracted) -} diff --git a/src/lib.rs b/src/lib.rs index a450f00..ec41692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ use crate::bmp::FirmwareType; pub mod bmp; mod bmp_matcher; pub mod docs_viewer; -pub mod elf; pub mod error; pub mod firmware_file; pub mod firmware_selector; From c3ec9e01f7b96977ae9ecbf9c7942c771a72c744 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Tue, 16 Dec 2025 23:14:58 -0800 Subject: [PATCH 10/17] firmware_type: Refactored the FirmwareType enum and logic into its own file --- src/bmp.rs | 135 +------------------------------------------ src/firmware_type.rs | 131 +++++++++++++++++++++++++++++++++++++++++ src/flasher.rs | 3 +- src/lib.rs | 5 +- 4 files changed, 139 insertions(+), 135 deletions(-) create mode 100644 src/firmware_type.rs diff --git a/src/bmp.rs b/src/bmp.rs index d47a719..c0945ac 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -3,16 +3,13 @@ // SPDX-FileContributor: Written by Mikaela Szekely // SPDX-FileContributor: Modified by Rachel Mant -use std::array::TryFromSliceError; use std::cell::{Ref, RefCell}; use std::fmt::{self, Debug, Display, Formatter}; use std::io::Read; use std::thread; use std::time::{Duration, Instant}; -use clap::ValueEnum; -use clap::builder::PossibleValue; -use color_eyre::eyre::{Context, Error, OptionExt, Result, eyre}; +use color_eyre::eyre::{Context, Error, OptionExt, Result}; use dfu_core::{DfuIo, DfuProtocol, Error as DfuCoreError, State as DfuState}; use dfu_nusb::{DfuNusb, DfuSync, Error as DfuNusbError}; use log::{debug, error, trace, warn}; @@ -22,6 +19,8 @@ use nusb::{Device, DeviceInfo, Interface}; pub use crate::bmp_matcher::BmpMatcher; use crate::error::ErrorKind; +// XXX: Ideally this shouldn't be pub here, but that's a breaking change. +pub use crate::firmware_type::FirmwareType; use crate::probe_identity::ProbeIdentity; use crate::serial::bmd_rsp::BmdRspInterface; use crate::serial::gdb_rsp::GdbRspInterface; @@ -631,134 +630,6 @@ impl Display for BmpDevice } } -/// Represents a conceptual Vector Table for Armv7 processors. -pub struct Armv7mVectorTable<'b> -{ - bytes: &'b [u8], -} - -impl<'b> Armv7mVectorTable<'b> -{ - fn word(&self, index: usize) -> Result - { - let start = index * 4; - let array: [u8; 4] = self.bytes[(start)..(start + 4)].try_into()?; - - Ok(u32::from_le_bytes(array)) - } - - /// Construct a conceptual Armv7m Vector Table from a bytes slice. - pub fn from_bytes(bytes: &'b [u8]) -> Self - { - if bytes.len() < (4 * 2) { - panic!("Data passed is not long enough for an Armv7m Vector Table!"); - } - - Self { - bytes, - } - } - - pub fn stack_pointer(&self) -> Result - { - self.word(0) - } - - pub fn reset_vector(&self) -> Result - { - self.word(1) - } - - pub fn exception(&self, exception_number: u32) -> Result - { - self.word((exception_number + 1) as usize) - } -} - -/// Firmware types for the Black Magic Probe. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum FirmwareType -{ - /// The bootloader. For native probes this is linked at 0x0800_0000 - Bootloader, - /// The main application. For native probes this is linked at 0x0800_2000. - Application, -} - -impl FirmwareType -{ - /// Detect the kind of firmware from the given binary by examining its reset vector address. - /// - /// This function panics if `firmware.len() < 8`. - pub fn detect_from_firmware(platform: BmpPlatform, firmware: &[u8]) -> Result - { - let buffer = &firmware[0..(4 * 2)]; - - let vector_table = Armv7mVectorTable::from_bytes(buffer); - let reset_vector = vector_table - .reset_vector() - .wrap_err("Firmware file does not seem valid: vector table too short")?; - - debug!("Detected reset vector in firmware file: 0x{:08x}", reset_vector); - - // Sanity check. - if (reset_vector & 0x0800_0000) != 0x0800_0000 { - return Err(eyre!( - "Firmware file does not seem valid: reset vector address seems to be outside of reasonable bounds - \ - 0x{:08x}", - reset_vector - )); - } - - let app_start = platform.load_address(Self::Application); - - if reset_vector > app_start { - Ok(Self::Application) - } else { - Ok(Self::Bootloader) - } - } -} - -impl Display for FirmwareType -{ - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result - { - match self { - Self::Bootloader => write!(f, "bootloader")?, - Self::Application => write!(f, "application")?, - }; - - Ok(()) - } -} - -/// Defaults to [`FirmwareType::Application`]. -impl Default for FirmwareType -{ - /// Defaults to [`FirmwareType::Application`]. - fn default() -> Self - { - FirmwareType::Application - } -} - -impl ValueEnum for FirmwareType -{ - fn value_variants<'a>() -> &'a [Self] - { - &[Self::Application, Self::Bootloader] - } - - fn to_possible_value(&self) -> Option - { - match self { - Self::Bootloader => Some("bootloader".into()), - Self::Application => Some("application".into()), - } - } -} - /// Waits for a Black Magic Probe to reboot, erroring after a timeout. /// /// This function takes a port identifier to attempt to keep track of a single physical device diff --git a/src/firmware_type.rs b/src/firmware_type.rs new file mode 100644 index 0000000..4414dc5 --- /dev/null +++ b/src/firmware_type.rs @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2022-2025 1BitSquared +// SPDX-FileContributor: Written by Mikaela Szekely +// SPDX-FileContributor: Modified by Rachel Mant + +use std::array::TryFromSliceError; +use std::fmt::Display; + +use clap::{ValueEnum, builder::PossibleValue}; +use color_eyre::eyre::{Context, Result, eyre}; +use log::debug; + +use crate::bmp::BmpPlatform; + +/// Represents a conceptual Vector Table for Armv7 processors. +pub struct Armv7mVectorTable<'b> +{ + bytes: &'b [u8], +} + +impl<'b> Armv7mVectorTable<'b> +{ + fn word(&self, index: usize) -> Result + { + let start = index * 4; + let array: [u8; 4] = self.bytes[(start)..(start + 4)].try_into()?; + + Ok(u32::from_le_bytes(array)) + } + + /// Construct a conceptual Armv7m Vector Table from a bytes slice. + pub fn from_bytes(bytes: &'b [u8]) -> Self + { + if bytes.len() < (4 * 2) { + panic!("Data passed is not long enough for an Armv7m Vector Table!"); + } + + Self { + bytes, + } + } + + pub fn stack_pointer(&self) -> Result + { + self.word(0) + } + + pub fn reset_vector(&self) -> Result + { + self.word(1) + } + + pub fn exception(&self, exception_number: u32) -> Result + { + self.word((exception_number + 1) as usize) + } +} + +/// Firmware types for the Black Magic Probe. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum FirmwareType +{ + /// The bootloader. For native probes this is linked at 0x0800_0000 + Bootloader, + /// The main application. For native probes this is linked at 0x0800_2000. + Application, +} + +impl FirmwareType +{ + /// Detect the kind of firmware from the given binary by examining its reset vector address. + /// + /// This function panics if `firmware.len() < 8`. + pub fn detect_from_firmware(platform: BmpPlatform, firmware: &[u8]) -> Result + { + let buffer = &firmware[0..(4 * 2)]; + + let vector_table = Armv7mVectorTable::from_bytes(buffer); + let reset_vector = vector_table + .reset_vector() + .wrap_err("Firmware file does not seem valid: vector table too short")?; + + debug!("Detected reset vector in firmware file: 0x{:08x}", reset_vector); + + // Sanity check. + if (reset_vector & 0x0800_0000) != 0x0800_0000 { + return Err(eyre!( + "Firmware file does not seem valid: reset vector address seems to be outside of reasonable bounds - \ + 0x{:08x}", + reset_vector + )); + } + + let app_start = platform.load_address(Self::Application); + + if reset_vector > app_start { + Ok(Self::Application) + } else { + Ok(Self::Bootloader) + } + } +} + +impl Display for FirmwareType +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + { + match self { + Self::Bootloader => write!(f, "bootloader")?, + Self::Application => write!(f, "application")?, + }; + + Ok(()) + } +} + +impl ValueEnum for FirmwareType +{ + fn value_variants<'a>() -> &'a [Self] + { + &[Self::Application, Self::Bootloader] + } + + fn to_possible_value(&self) -> Option + { + match self { + Self::Bootloader => Some("bootloader".into()), + Self::Application => Some("application".into()), + } + } +} diff --git a/src/flasher.rs b/src/flasher.rs index 17e7737..790c4be 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -14,8 +14,9 @@ use color_eyre::owo_colors::OwoColorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, warn}; -use crate::bmp::{self, BmpDevice, FirmwareType}; +use crate::bmp::{self, BmpDevice}; use crate::firmware_file::FirmwareFile; +use crate::firmware_type::FirmwareType; use crate::usb::PortId; use crate::{AllowDangerous, BmpParams, FlashParams}; diff --git a/src/lib.rs b/src/lib.rs index ec41692..5f8a657 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,15 @@ use clap::ValueEnum; use clap::builder::PossibleValue; -use crate::bmp::FirmwareType; +use crate::firmware_type::FirmwareType; pub mod bmp; mod bmp_matcher; pub mod docs_viewer; pub mod error; -pub mod firmware_file; +mod firmware_file; pub mod firmware_selector; +pub mod firmware_type; pub mod flasher; pub mod metadata; pub mod probe_identity; From 3eaca54064860b2661674d107984c7f9cfcadb83 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:12:29 -0800 Subject: [PATCH 11/17] firmware_type/raw: Added some validation on the length of the data read in to ensure it's not impossibly large --- src/firmware_file/raw.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs index 1032bce..1de212a 100644 --- a/src/firmware_file/raw.rs +++ b/src/firmware_file/raw.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::Read; -use color_eyre::eyre::{Report, Result}; +use color_eyre::eyre::{Report, Result, eyre}; use log::debug; use super::FirmwareStorage; @@ -26,10 +26,17 @@ impl TryFrom for RawFirmwareFile let mut contents = Vec::new(); file.read_to_end(&mut contents)?; - // Put the vec inside our little container and be done - Ok(Self { - contents: contents.into_boxed_slice(), - }) + // Check that the result isn't too insanely big + if contents.len() > u32::MAX as usize { + Err(eyre!( + "Firmware file size exceeds the max value for a 32-bit integer! Firmware binary invalid (too big)" + )) + } else { + // Otherwise put the vec inside our little container and be done + Ok(Self { + contents: contents.into_boxed_slice(), + }) + } } } From 0732eb1a98e6e990319b89862139a1b4fc98adc7 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:12:55 -0800 Subject: [PATCH 12/17] firmware_type: Implemented a helper for getting the length of the firmware as a u32 --- src/firmware_file/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs index b0f04fe..5ec0161 100644 --- a/src/firmware_file/mod.rs +++ b/src/firmware_file/mod.rs @@ -66,4 +66,9 @@ impl FirmwareFile { self.inner.firmware_data() } + + pub fn firmware_len(&self) -> u32 + { + self.inner.firmware_data().len() as u32 + } } From 8e9a1e0456725d1f1b3e5b63c28a6646de0e526f Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:13:59 -0800 Subject: [PATCH 13/17] flasher: Refactored the firmware storage to hold a FirmwareFile, rather than decomposing the file --- src/bmp.rs | 8 +++----- src/flasher.rs | 22 ++++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/bmp.rs b/src/bmp.rs index c0945ac..a50cd9f 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -496,22 +496,20 @@ impl BmpDevice /// /// `progress` is a callback of the form `fn(just_written: usize)`, for callers to keep track of /// the flashing process. - pub fn download<'r, R, P>( + pub fn download<'r, P>( &mut self, - firmware: &'r R, - length: u32, + firmware: &'r [u8], firmware_type: FirmwareType, progress: P, ) -> Result where - &'r R: Read, - R: ?Sized, P: Fn(usize) + 'static, { if self.mode == DfuOperatingMode::Runtime { self.detach_and_enumerate().wrap_err("detaching device for download")?; } + let length = firmware.len() as u32; let load_address = self.platform.load_address(firmware_type); let dfu_dev = DfuNusb::open( diff --git a/src/flasher.rs b/src/flasher.rs index 790c4be..427dcf3 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -23,8 +23,7 @@ use crate::{AllowDangerous, BmpParams, FlashParams}; pub struct Firmware { firmware_type: FirmwareType, - data: Vec, - length: u32, + firmware_file: FirmwareFile, } impl Firmware @@ -34,14 +33,10 @@ impl Firmware Params: BmpParams + FlashParams, { let firmware_data = firmware_file.firmware_data(); - let firmware_length = firmware_data.len(); - let firmware_length = u32::try_from(firmware_length) - .expect("firmware filesize exceeded 32 bits! Firmware binary must be invalid (too big)"); Ok(Self { firmware_type: Self::determine_firmware_type(params, device, firmware_data)?, - data: firmware_data.to_vec(), - length: firmware_length, + firmware_file, }) } @@ -93,21 +88,24 @@ impl Firmware pub fn program_firmware(&self, device: &mut BmpDevice) -> Result<()> { + // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda + let firmware_type = self.firmware_type; + // Pull out the data to program + let firmware_data = self.firmware_file.firmware_data(); + // We need an Rc as [`dfu_core::sync::DfuSync`] requires `progress` to be 'static, // so it must be moved into the closure. However, since we need to call .finish() here, // it must be owned by both. Hence: Rc. // Default template: `{wide_bar} {pos}/{len}`. - let progress_bar = ProgressBar::new(self.length as u64).with_style( + let progress_bar = ProgressBar::new(firmware_data.len() as u64).with_style( ProgressStyle::default_bar() .template(" {percent:>3}% |{bar:50}| {bytes}/{total_bytes} [{binary_bytes_per_sec} {elapsed}]") .unwrap(), ); let progress_bar = Rc::new(progress_bar); let enclosed = Rc::clone(&progress_bar); - // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda - let firmware_type = self.firmware_type; - let result = device.download(&*self.data, self.length, firmware_type, move |flash_pos_delta| { + let result = device.download(firmware_data, firmware_type, move |flash_pos_delta| { // Don't actually print flashing until the erasing has finished. if enclosed.position() == 0 { if firmware_type == FirmwareType::Application { @@ -122,7 +120,7 @@ impl Firmware let dfu_iface = result?; info!("Flash complete!"); - if progress_bar.position() == (self.length as u64) { + if progress_bar.position() == (firmware_data.len() as u64) { device.reboot(dfu_iface) } else { Err(eyre!("Failed to flash device, download incomplete")) From 8c08be81465865042790f5996da46587905abfa7 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:23:26 -0800 Subject: [PATCH 14/17] firmware_type: Improved handling of determining what kind of firmware image we're playing with --- src/firmware_type.rs | 21 ++++++++++++++++++--- src/flasher.rs | 8 +++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/firmware_type.rs b/src/firmware_type.rs index 4414dc5..fd5187b 100644 --- a/src/firmware_type.rs +++ b/src/firmware_type.rs @@ -10,7 +10,7 @@ use clap::{ValueEnum, builder::PossibleValue}; use color_eyre::eyre::{Context, Result, eyre}; use log::debug; -use crate::bmp::BmpPlatform; +use crate::{bmp::BmpPlatform, firmware_file::FirmwareFile}; /// Represents a conceptual Vector Table for Armv7 processors. pub struct Armv7mVectorTable<'b> @@ -71,9 +71,24 @@ impl FirmwareType /// Detect the kind of firmware from the given binary by examining its reset vector address. /// /// This function panics if `firmware.len() < 8`. - pub fn detect_from_firmware(platform: BmpPlatform, firmware: &[u8]) -> Result + pub fn detect_from_firmware(platform: BmpPlatform, firmware_file: &FirmwareFile) -> Result { - let buffer = &firmware[0..(4 * 2)]; + // If the firmware image has a load address + if let Some(load_address) = firmware_file.load_address() { + // Check if the address is the bootloader area for the platform + let boot_start = platform.load_address(Self::Bootloader); + return Ok( + if load_address == boot_start { + Self::Bootloader + } else { + Self::Application + } + ); + } + + // If the firmware doesn't have a known load address, fall back to figuring it out + // from the NVIC table at the front of the image + let buffer = &firmware_file.firmware_data()[0..(4 * 2)]; let vector_table = Armv7mVectorTable::from_bytes(buffer); let reset_vector = vector_table diff --git a/src/flasher.rs b/src/flasher.rs index 427dcf3..2c04ae7 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -32,10 +32,8 @@ impl Firmware where Params: BmpParams + FlashParams, { - let firmware_data = firmware_file.firmware_data(); - Ok(Self { - firmware_type: Self::determine_firmware_type(params, device, firmware_data)?, + firmware_type: Self::determine_firmware_type(params, device, &firmware_file)?, firmware_file, }) } @@ -43,7 +41,7 @@ impl Firmware fn determine_firmware_type( params: &Params, device: &BmpDevice, - firmware_data: &[u8], + firmware_file: &FirmwareFile, ) -> Result where Params: BmpParams + FlashParams, @@ -52,7 +50,7 @@ impl Firmware // Using the platform to determine the link address. let platform = device.platform(); let firmware_type = - FirmwareType::detect_from_firmware(platform, firmware_data).wrap_err("detecting firmware type")?; + FirmwareType::detect_from_firmware(platform, firmware_file).wrap_err("detecting firmware type")?; debug!("Firmware file was detected as {}", firmware_type); From d3151c17f48648bcca4a85fd5dc16cfb5943eb09 Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:38:43 -0800 Subject: [PATCH 15/17] firmware_file: Renamed the accessors for the firmware data and length so they're more sensible on use --- src/firmware_file/mod.rs | 7 ++++--- src/firmware_type.rs | 2 +- src/flasher.rs | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs index 5ec0161..9855881 100644 --- a/src/firmware_file/mod.rs +++ b/src/firmware_file/mod.rs @@ -60,14 +60,15 @@ impl FirmwareFile self.inner.load_address() } - /// Provides the firmware data this file holds in a format suitable for + /// Provides the firmware image data this file holds in a format suitable for /// writing into Flash directly at the load address - pub fn firmware_data(&self) -> &[u8] + pub fn data(&self) -> &[u8] { self.inner.firmware_data() } - pub fn firmware_len(&self) -> u32 + /// Provides the length of the firmware image this file holds as a u32 + pub fn len(&self) -> u32 { self.inner.firmware_data().len() as u32 } diff --git a/src/firmware_type.rs b/src/firmware_type.rs index fd5187b..f5f7e7b 100644 --- a/src/firmware_type.rs +++ b/src/firmware_type.rs @@ -88,7 +88,7 @@ impl FirmwareType // If the firmware doesn't have a known load address, fall back to figuring it out // from the NVIC table at the front of the image - let buffer = &firmware_file.firmware_data()[0..(4 * 2)]; + let buffer = &firmware_file.data()[0..(4 * 2)]; let vector_table = Armv7mVectorTable::from_bytes(buffer); let reset_vector = vector_table diff --git a/src/flasher.rs b/src/flasher.rs index 2c04ae7..1aafe5b 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -38,6 +38,7 @@ impl Firmware }) } + // XXX: Move me to FirmwareFile? fn determine_firmware_type( params: &Params, device: &BmpDevice, @@ -89,7 +90,7 @@ impl Firmware // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda let firmware_type = self.firmware_type; // Pull out the data to program - let firmware_data = self.firmware_file.firmware_data(); + let firmware_data = self.firmware_file.data(); // We need an Rc as [`dfu_core::sync::DfuSync`] requires `progress` to be 'static, // so it must be moved into the closure. However, since we need to call .finish() here, From 4c8bd5621edcf4cecbf3ab83ec8180790825223c Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:39:47 -0800 Subject: [PATCH 16/17] flasher: Cleaned up the usage of the firmware length in `Firmware::program_firmware()` --- src/flasher.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/flasher.rs b/src/flasher.rs index 1aafe5b..511a9f6 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -91,12 +91,14 @@ impl Firmware let firmware_type = self.firmware_type; // Pull out the data to program let firmware_data = self.firmware_file.data(); + // Pull out the length of the firmware image and make it a u64 + let firmware_length = self.firmware_file.len() as u64; // We need an Rc as [`dfu_core::sync::DfuSync`] requires `progress` to be 'static, // so it must be moved into the closure. However, since we need to call .finish() here, // it must be owned by both. Hence: Rc. // Default template: `{wide_bar} {pos}/{len}`. - let progress_bar = ProgressBar::new(firmware_data.len() as u64).with_style( + let progress_bar = ProgressBar::new(firmware_length).with_style( ProgressStyle::default_bar() .template(" {percent:>3}% |{bar:50}| {bytes}/{total_bytes} [{binary_bytes_per_sec} {elapsed}]") .unwrap(), @@ -119,7 +121,7 @@ impl Firmware let dfu_iface = result?; info!("Flash complete!"); - if progress_bar.position() == (firmware_data.len() as u64) { + if progress_bar.position() == firmware_length { device.reboot(dfu_iface) } else { Err(eyre!("Failed to flash device, download incomplete")) From a952aba134a512b7cbbeb1bb911251cb71c7e58c Mon Sep 17 00:00:00 2001 From: dragonmux Date: Wed, 17 Dec 2025 00:40:33 -0800 Subject: [PATCH 17/17] bmp: Made `BmpDevice::download()` take a FirmwareFile and fixed the load address problem --- src/bmp.rs | 16 ++++++++++------ src/flasher.rs | 4 +--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/bmp.rs b/src/bmp.rs index a50cd9f..6672ef7 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -19,6 +19,7 @@ use nusb::{Device, DeviceInfo, Interface}; pub use crate::bmp_matcher::BmpMatcher; use crate::error::ErrorKind; +use crate::firmware_file::FirmwareFile; // XXX: Ideally this shouldn't be pub here, but that's a breaking change. pub use crate::firmware_type::FirmwareType; use crate::probe_identity::ProbeIdentity; @@ -496,9 +497,9 @@ impl BmpDevice /// /// `progress` is a callback of the form `fn(just_written: usize)`, for callers to keep track of /// the flashing process. - pub fn download<'r, P>( + pub fn download

( &mut self, - firmware: &'r [u8], + firmware_file: &FirmwareFile, firmware_type: FirmwareType, progress: P, ) -> Result @@ -509,8 +510,11 @@ impl BmpDevice self.detach_and_enumerate().wrap_err("detaching device for download")?; } - let length = firmware.len() as u32; - let load_address = self.platform.load_address(firmware_type); + // Extract the file's load address, and if that isn't a thing, fall back on + // more crude methods based on the platform we're downloading to + let load_address = firmware_file + .load_address() + .unwrap_or_else(|| self.platform.load_address(firmware_type)); let dfu_dev = DfuNusb::open( self.device.take().expect("Must have a valid device handle"), @@ -531,7 +535,7 @@ impl BmpDevice debug!("Load address: 0x{:08x}", load_address); - match self.try_download(firmware, length, &mut dfu_iface) { + match self.try_download(firmware_file.data(), firmware_file.len(), &mut dfu_iface) { Err(error) => { if let Some(DfuNusbError::Dfu(DfuCoreError::StateError(DfuState::DfuError))) = error.downcast_ref::() @@ -555,7 +559,7 @@ impl BmpDevice .unwrap() .control_out_blocking(request, &[], Duration::from_secs(2))?; - self.try_download(firmware, length, &mut dfu_iface) + self.try_download(firmware_file.data(), firmware_file.len(), &mut dfu_iface) } else { Err(error) } diff --git a/src/flasher.rs b/src/flasher.rs index 511a9f6..f3abbff 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -89,8 +89,6 @@ impl Firmware { // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda let firmware_type = self.firmware_type; - // Pull out the data to program - let firmware_data = self.firmware_file.data(); // Pull out the length of the firmware image and make it a u64 let firmware_length = self.firmware_file.len() as u64; @@ -106,7 +104,7 @@ impl Firmware let progress_bar = Rc::new(progress_bar); let enclosed = Rc::clone(&progress_bar); - let result = device.download(firmware_data, firmware_type, move |flash_pos_delta| { + let result = device.download(&self.firmware_file, firmware_type, move |flash_pos_delta| { // Don't actually print flashing until the erasing has finished. if enclosed.position() == 0 { if firmware_type == FirmwareType::Application {