From ac331fc3a82d9b5ecae43931cb4ea8731270f8f1 Mon Sep 17 00:00:00 2001 From: Keaton Clark Date: Sun, 3 Aug 2025 19:21:12 -0700 Subject: [PATCH 1/4] .resource_table section parsing --- sample-objects/arm64-main-r5f0-0-fw.armhf | Bin 0 -> 83232 bytes src/abi.rs | 14 + src/elf_bytes.rs | 77 +++ src/lib.rs | 1 + src/resource_table.rs | 593 ++++++++++++++++++++++ 5 files changed, 685 insertions(+) create mode 100644 sample-objects/arm64-main-r5f0-0-fw.armhf create mode 100644 src/resource_table.rs diff --git a/sample-objects/arm64-main-r5f0-0-fw.armhf b/sample-objects/arm64-main-r5f0-0-fw.armhf new file mode 100644 index 0000000000000000000000000000000000000000..392b493d1e49f4ffe4f66e030eb6404d5e4582da GIT binary patch literal 83232 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 literal 0 HcmV?d00001 diff --git a/src/abi.rs b/src/abi.rs index 3ffcfc3..87ed6a1 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -3146,6 +3146,20 @@ pub const DT_MIPS_DIRECT: u32 = 0x70000032; /// The dynamic symbol entry of a callback function pub const DT_MIPS_RLD_OBJ_UPDATE: u32 = 0x70000033; +// resource_type in FirmwareResource +/// Request for allocation of a physically contiguous memory region. +pub const RSC_CARVEOUT: u32 = 0; +/// Request to iommu_map a memory-based peripheral. +pub const RSC_DEVMEM: u32 = 1; +/// Announces the availability of a trace buffer into which the remote processor will be writing logs. +pub const RSC_TRACE: u32 = 2; +/// Declare support for a virtio device, and serve as its virtio header. +pub const RSC_VDEV: u32 = 3; +/// Start of the vendor specific resource types range +pub const RSC_VENDOR_START: u32 = 128; +/// End of the vendor specific resource types range +pub const RSC_VENDOR_END: u32 = 512; + /// None pub const RHF_NONE: u32 = 0x00000000; /// Use runtime loading shortcuts if possible diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 6444e76..02b80f9 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -10,6 +10,7 @@ use crate::hash::{GnuHashTable, SysVHashTable}; use crate::note::NoteIterator; use crate::parse::{ParseAt, ParseError, ReadBytesExt}; use crate::relocation::{RelIterator, RelaIterator}; +use crate::resource_table::ResourceTable; use crate::section::{SectionHeader, SectionHeaderTable}; use crate::segment::{ProgramHeader, SegmentTable}; use crate::string_table::StringTable; @@ -549,6 +550,32 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { )) } + + /// Get the section data for a given [SectionHeader], and interpret it as a + /// [ResourceTable](crate::resource_table::ResourceTable) + /// + /// Returns a ParseError if the section is not of type [abi::SHT_PROGBITS] + pub fn section_data_as_resource_table( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_PROGBITS { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_PROGBITS, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + let mut offset = 0; + ResourceTable::parse_at( + self.ehdr.endianness, + self.ehdr.class, + &mut offset, + buf, + ) + } + /// Internal helper to get the section data for an SHT_DYNAMIC section as a .dynamic section table. /// See [ElfBytes::dynamic] or [ElfBytes::find_common_data] for the public interface fn section_data_as_dynamic( @@ -1200,6 +1227,56 @@ mod interface_tests { assert!(notes.next().is_none()); } + #[test] + fn section_data_as_resource_table() { + use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring, FW_RSC_ADDR_ANY}; + let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have a section table") + .get(24) + .expect("Failed to get .resource_table shdr"); + + let resource_table = file + .section_data_as_resource_table(&shdr) + .expect("Failed to read note section"); + + let mut it = (&resource_table).into_iter(); + if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { + assert_eq!(vdev.id, 7); + assert_eq!(vdev.notify_id, 0); + assert_eq!(vdev.dfeatures, 1); + assert_eq!(vdev.num_of_vrings, 2); + let mut it = (&vdev).into_iter(); + + let vdev_vring1 = it.next().unwrap(); + assert_eq!(vdev_vring1, + FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 1, pa: 0 } + ); + + let vdev_vring2 = it.next().unwrap(); + assert_eq!(vdev_vring2, + FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 2, pa: 0 } + ); + assert!(it.next().is_none()); + } else { + panic!(".resource_table parsed incorrectly"); + } + + if let FirmwareResource::Trace(trace) = it.next().unwrap() { + assert_eq!(trace.da, 0xA0106080); + assert_eq!(trace.len, 0x1000); + let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); + assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); + } else { + panic!(".resource_table parsed incorrectly"); + } + } + #[test] fn segment_data_as_notes() { let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); diff --git a/src/lib.rs b/src/lib.rs index 75cfad8..cc19800 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,7 @@ pub mod gnu_symver; pub mod hash; pub mod note; pub mod relocation; +pub mod resource_table; pub mod section; pub mod segment; pub mod string_table; diff --git a/src/resource_table.rs b/src/resource_table.rs new file mode 100644 index 0000000..c90ac11 --- /dev/null +++ b/src/resource_table.rs @@ -0,0 +1,593 @@ +use crate::{ + abi::{RSC_CARVEOUT, RSC_VDEV, RSC_TRACE, RSC_DEVMEM}, endian::EndianParse, file::Class, parse::{ParseAt, ReadBytesExt}, ParseError +}; + +/// Firmware resource table header +/// The offsets and entries are left as raw bytes due to the dynamic nature of the possible +/// variants and lengths, this means each entry needs to be parsed when iterating +/// The entire table is parsed when constructed with `parse_at` so that parsing errors are not +/// encountered during iterating, which has no error state +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ResourceTable<'data, E: EndianParse> { + /// Version number + pub version: u32, + /// Number of resource entries + pub num_resources: u32, + /// Array of offsets pointing at the various resource entries + /// These offsets are from the start of the section, so you must subtract + /// (size_of::() * 4) + resource_offsets.len() to index into the resource_entries + /// These are u32s in the endian of the elf, so they must be parsed as they are needed + pub resource_offsets: &'data [u8], + /// The rest of the section, to be parsed into FirmwareResource as needed + pub resource_entries: &'data [u8], + + /// Used to create the iterator later + endian: E, + /// Used to create the iterator later + class: Class, +} + +impl<'data, E: EndianParse> ResourceTable<'data, E> { + pub fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let version = endian.parse_u32_at(offset, data)?; + let num_resources = endian.parse_u32_at(offset, data)?; + + let _reserved = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + + let resource_offsets = { + let resource_offsets_start = *offset; + let resource_offsets_end = + resource_offsets_start + (size_of::() * num_resources as usize); + *offset = resource_offsets_end; + &data.get_bytes(resource_offsets_start..resource_offsets_end)? + }; + + let resource_entries = { + let resource_entries_start = *offset; + let resource_entries_end = data.len(); + *offset = resource_entries_end; + &data.get_bytes(resource_entries_start..resource_entries_end)? + }; + // Parse all resource entries + // The itererator cannot return an error state so the checks are done here + let resource_table = ResourceTable { + version, + num_resources, + resource_offsets, + resource_entries, + endian, + class, + }; + for i in 0..num_resources { + let _ = resource_table.get(i as usize)?; + } + Ok(resource_table) + } + fn get( + &self, + entry: usize, + ) -> Result, ParseError> { + if entry >= self.num_resources as usize { + return Err(ParseError::BadOffset(entry as u64)); + } + + // offset into the resource_entries + let mut resource_offsets_offset: usize = entry * size_of::(); + + // offset into the resource_entries + let mut resource_entry_offset = self.endian + .parse_u32_at(&mut resource_offsets_offset, self.resource_offsets)? as usize; + // Subtract the header size + resource_entry_offset = resource_entry_offset + - ((size_of::() * 4) + self.resource_offsets.len()) as usize; + + FirmwareResource::parse_at( + self.endian, + self.class, + &mut resource_entry_offset, + self.resource_entries, + ) + } + pub fn to_iter( + &'data self, + ) -> FirmwareResourceIterator<'data, E> { + FirmwareResourceIterator { + idx: 0, + resource_table: self, + } + } +} + +impl<'data, E: EndianParse> IntoIterator for &'data ResourceTable<'data, E> { + type Item = FirmwareResource<'data, E>; + type IntoIter = FirmwareResourceIterator<'data, E>; + fn into_iter(self) -> Self::IntoIter { + self.to_iter() + } +} + +impl<'data, E: EndianParse> Iterator for FirmwareResourceIterator<'data, E> { + type Item = FirmwareResource<'data, E>; + fn next(&mut self) -> Option { + let out = self.resource_table.get(self.idx).ok(); + self.idx += 1; + out + } +} + +#[derive(Debug)] +pub struct FirmwareResourceIterator<'data, E: EndianParse> { + idx: usize, + resource_table: &'data ResourceTable<'data, E>, +} + +/// This enum contains parsed firmware resource variants that can be matched on +#[derive(Debug, PartialEq, Eq)] +pub enum FirmwareResource<'data, E: EndianParse> { + Carveout(FirmwareResourceCarveout<'data>), + Devmem(FirmwareResourceDevmem<'data>), + Trace(FirmwareResourceTrace<'data>), + Vdev(FirmwareResourceVdev<'data, E>), + /// Represents Resource with an unsupported type + /// These are usually vendor defined resources + Unknown(u32), +} + +pub const FW_RSC_ADDR_ANY: u32 = u32::MAX; + +impl<'data, E: EndianParse> FirmwareResource<'data, E> { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + // Can break this out to enum if needed + let resource_type = endian.parse_u32_at(offset, data)?; + match resource_type { + RSC_CARVEOUT => Ok(FirmwareResource::Carveout( + FirmwareResourceCarveout::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceCarveout::size_for(class)], + )?, + )), + RSC_DEVMEM => Ok(FirmwareResource::Devmem( + FirmwareResourceDevmem::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceDevmem::size_for(class)], + )? + )), + RSC_TRACE => Ok(FirmwareResource::Trace( + FirmwareResourceTrace::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceTrace::size_for(class)], + )? + )), + RSC_VDEV => Ok(FirmwareResource::Vdev( + FirmwareResourceVdev::parse_at( + endian, + class, + offset, + &data, + )? + )), + _ => Ok(FirmwareResource::Unknown(resource_type)), + } + } +} + +/// physically contiguous memory request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceCarveout<'data> { + /// Device address + pub da: u32, + /// Physical address + pub pa: u32, + /// Length in bytes + pub len: u32, + /// iommu protection flags + pub flags: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} +impl<'data> FirmwareResourceCarveout<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 5) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let flags = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceCarveout { + da, + pa, + len, + flags, + name, + }) + } +} + +/// iommu mapping request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceDevmem<'data> { + /// Device address + pub da: u32, + /// Physical address + pub pa: u32, + /// Length in bytes + pub len: u32, + /// iommu protection flags + pub flags: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} + +impl<'data> FirmwareResourceDevmem<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 5) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let flags = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceDevmem { + da, + pa, + len, + flags, + name, + }) + } +} + + +/// trace buffer declaration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceTrace<'data> { + /// Device address + pub da: u32, + /// Length in bytes + pub len: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} + +impl<'data> FirmwareResourceTrace<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 3) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceTrace { da, len, name }) + } +} + +/// vring descriptor entry +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceVdevVring { + /// Device address + pub da: u32, + /// The alignment between the consumer and producer parts of the vring + pub align: u32, + /// Number of buffers supported by this vring (must be power of two) + pub num: u32, + /// A unique rproc-wide notify index for this vring. This notify + /// index is used when kicking a remote processor, to let it know that this + /// vring is triggered + pub notify_id: u32, + /// physical address + pub pa: u32, +} + +impl ParseAt for FirmwareResourceVdevVring { + fn size_for(_class: Class) -> usize { + size_of::() * 5 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let align = endian.parse_u32_at(offset, data)?; + let num = endian.parse_u32_at(offset, data)?; + let notify_id = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + + Ok(FirmwareResourceVdevVring { + da, + align, + num, + notify_id, + pa, + }) + } +} + +impl<'data, E: EndianParse> IntoIterator for &'data FirmwareResourceVdev<'data, E> { + type Item = FirmwareResourceVdevVring; + type IntoIter = FirmwareResourceVdevVringIterator<'data, E>; + fn into_iter(self) -> Self::IntoIter { + self.to_iter() + } +} + +impl<'data, E: EndianParse> Iterator for FirmwareResourceVdevVringIterator<'data, E> { + type Item = FirmwareResourceVdevVring; + fn next(&mut self) -> Option { + let out = self.firmware_resource_vdev.get(self.idx).ok(); + self.idx += 1; + out + } +} + +#[derive(Debug)] +pub struct FirmwareResourceVdevVringIterator<'data, E: EndianParse> { + idx: usize, + firmware_resource_vdev: &'data FirmwareResourceVdev<'data, E>, +} + +/// virtio device header +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceVdev<'data, E: EndianParse> { + /// virtio device id (as in virtio_ids.h) + pub id: u32, + /// A unique rproc-wide notify index for this vdev. This notify + /// index is used when kicking a remote processor, to let it know that the + /// status/features of this vdev have changes. + pub notify_id: u32, + /// Specifies the virtio device features supported by the firmware + pub dfeatures: u32, + /// A place holder used by the host to write back the + /// negotiated features that are supported by both sides. + pub gfeatures: u32, + /// A place holder where the host will indicate its virtio progress. + pub status: u8, + /// Indicates how many vrings are described in this vdev header + pub num_of_vrings: u8, + /// An array of num_of_vrings entries + pub vrings: &'data [u8], + + /// Used to create the iterator later + endian: E, + /// Used to create the iterator later + class: Class, +} + +impl<'data, E: EndianParse> FirmwareResourceVdev<'data, E> { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let id = endian.parse_u32_at(offset, data)?; + let notify_id = endian.parse_u32_at(offset, data)?; + let dfeatures = endian.parse_u32_at(offset, data)?; + let gfeatures = endian.parse_u32_at(offset, data)?; + let config_len = endian.parse_u32_at(offset, data)?; + let status = endian.parse_u8_at(offset, data)?; + let num_of_vrings = endian.parse_u8_at(offset, data)?; + let _reserved = endian.parse_u8_at(offset, data)?; + let _reserved = endian.parse_u8_at(offset, data)?; + let vrings = data.get_bytes(*offset..*offset + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)) as usize)?; + *offset = *offset + config_len as usize; + Ok(FirmwareResourceVdev { + id, + notify_id, + dfeatures, + gfeatures, + num_of_vrings, + status, + vrings, + endian, + class, + }) + } + pub fn to_iter( + &'data self, + ) -> FirmwareResourceVdevVringIterator<'data, E> { + FirmwareResourceVdevVringIterator { + idx: 0, + firmware_resource_vdev: self, + } + } + fn get( + &self, + entry: usize, + ) -> Result { + if entry >= self.num_of_vrings as usize { + return Err(ParseError::BadOffset(entry as u64)); + } + + let mut offset = 0; + FirmwareResourceVdevVring::parse_at( + self.endian, + self.class, + &mut offset, + &self.vrings[entry * FirmwareResourceVdevVring::size_for(self.class)..(entry + 1) * FirmwareResourceVdevVring::size_for(self.class)], + ) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + + #[test] + fn parse_resource_table_hdr() { + #[rustfmt::skip] + let data = [ + 0x00, 0x00, 0x00, 0x01, // version + 0x00, 0x00, 0x00, 0x00, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + ]; + let mut offset = 0; + + let resource_table = ResourceTable::parse_at(BigEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 0); + assert_eq!(resource_table.resource_offsets.len(), 0); + } + + #[test] + fn parse_resource_table_fw_rsc_carveout() { + #[rustfmt::skip] + let data = [ + 0x00, 0x00, 0x00, 0x01, // version + 0x00, 0x00, 0x00, 0x01, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x14, // resource_offsets[0] == 20 + + 0x00, 0x00, 0x00, 0x00, // RSC_CARVEOUT == 0 + 0xDE, 0xAD, 0xBE, 0xEF, // da == 0xDEADBEEF + 0xAA, 0xBB, 0xCC, 0xDD, // pa == 0xAABBCCDD + 0xAA, 0xAA, 0xAA, 0xAA, // len == 0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, // flags == 0x00000000 + 0x00, 0x00, 0x00, 0x00, // reserved + 0x40, 0x00, 0x00, 0x00, // name: [u8;32] == "@" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let mut offset = 0; + + let resource_table = ResourceTable::parse_at(BigEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 1); + assert_eq!(resource_table.resource_offsets.len(), 1 * size_of::()); + + let resource_carveout = resource_table + .to_iter() + .next() + .expect("Could not get resource carveout"); + assert!(matches!( + resource_carveout, + FirmwareResource::Carveout(FirmwareResourceCarveout { + da: 0xDEADBEEF, + pa: 0xAABBCCDD, + len: 0xAAAAAAAA, + flags: 0x00000000, + name: _ + }) + )); + + if let FirmwareResource::Carveout(resource_carveout) = resource_carveout { + assert_eq!(resource_carveout.name[0], b'@'); + } + } + #[test] + fn parse_resource_table_fw_rsc_devmem() { + #[rustfmt::skip] + let data = [ + 0x01, 0x00, 0x00, 0x00, // version + 0x01, 0x00, 0x00, 0x00, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x14, 0x00, 0x00, 0x00, // resource_offsets[0] == 20 + + 0x01, 0x00, 0x00, 0x00, // RSC_DEVMEM == 1 + 0xEF, 0xBE, 0xAD, 0xDE, // da == 0xDEADBEEF + 0xDD, 0xCC, 0xBB, 0xAA, // pa == 0xAABBCCDD + 0xAA, 0xAA, 0xAA, 0xAA, // len == 0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, // flags == 0x00000000 + 0x00, 0x00, 0x00, 0x00, // reserved + 0x40, 0x00, 0x00, 0x00, // name: [u8;32] == "@" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let mut offset = 0; + + let resource_table = ResourceTable::parse_at(LittleEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 1); + assert_eq!(resource_table.resource_offsets.len(), 1 * size_of::()); + + let resource_devmem = resource_table + .to_iter() + .next() + .expect("Could not get resource carveout"); + assert!(matches!( + resource_devmem, + FirmwareResource::Devmem(FirmwareResourceDevmem { + da: 0xDEADBEEF, + pa: 0xAABBCCDD, + len: 0xAAAAAAAA, + flags: 0x00000000, + name: _ + }) + )); + + if let FirmwareResource::Devmem(resource_devmem) = resource_devmem { + assert_eq!(resource_devmem.name[0], b'@'); + } + } +} From d6f6317d3855b90a5ffd19be7f717cf3c2dc8120 Mon Sep 17 00:00:00 2001 From: Keaton Clark Date: Mon, 4 Aug 2025 20:51:43 -0700 Subject: [PATCH 2/4] Add more docs --- src/abi.rs | 3 +++ src/elf_bytes.rs | 6 +++--- src/resource_table.rs | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/abi.rs b/src/abi.rs index 87ed6a1..bdfc93b 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -3160,6 +3160,9 @@ pub const RSC_VENDOR_START: u32 = 128; /// End of the vendor specific resource types range pub const RSC_VENDOR_END: u32 = 512; +/// Tells that the firmware does not care about the location of the resource +pub const FW_RSC_ADDR_ANY: u32 = u32::MAX; + /// None pub const RHF_NONE: u32 = 0x00000000; /// Use runtime loading shortcuts if possible diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 02b80f9..6fe2812 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -855,7 +855,7 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { #[cfg(test)] mod interface_tests { use super::*; - use crate::abi::{SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB}; + use crate::abi::{FW_RSC_ADDR_ANY, SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB}; use crate::endian::AnyEndian; use crate::hash::sysv_hash; use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId}; @@ -1229,7 +1229,7 @@ mod interface_tests { #[test] fn section_data_as_resource_table() { - use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring, FW_RSC_ADDR_ANY}; + use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring}; let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); let file_data = std::fs::read(path).expect("Could not read file."); let slice = file_data.as_slice(); @@ -1243,7 +1243,7 @@ mod interface_tests { let resource_table = file .section_data_as_resource_table(&shdr) - .expect("Failed to read note section"); + .expect("Failed to read .resource_table section"); let mut it = (&resource_table).into_iter(); if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { diff --git a/src/resource_table.rs b/src/resource_table.rs index c90ac11..2209b93 100644 --- a/src/resource_table.rs +++ b/src/resource_table.rs @@ -1,3 +1,36 @@ +//! Parsing remoteproc resource_table: `.resource_table` +//! +//! Example for getting the trace information +//! ``` +//! use elf::ElfBytes; +//! use elf::endian::AnyEndian; +//! use elf::note::Note; +//! use elf::note::NoteGnuAbiTag; +//! use elf::resource_table::FirmwareResource; +//! +//! let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); +//! let file_data = std::fs::read(path).expect("Could not read file."); +//! let slice = file_data.as_slice(); +//! let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); +//! +//! let shdr = file +//! .section_header_by_name(".resource_table") +//! .expect("section table should be parseable") +//! .expect("file should have a .resource_table section"); +//! +//! let resource_table = file +//! .section_data_as_resource_table(&shdr) +//! .expect("Should be able to get .resource_table section data"); +//! let resources: Vec<_> = resource_table +//! .into_iter() +//! .collect(); +//! if let FirmwareResource::Trace(trace) = resources[1] { +//! assert_eq!(trace.da, 0xA0106080); +//! assert_eq!(trace.len, 0x1000); +//! let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); +//! assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); +//! } +//! ``` use crate::{ abi::{RSC_CARVEOUT, RSC_VDEV, RSC_TRACE, RSC_DEVMEM}, endian::EndianParse, file::Class, parse::{ParseAt, ReadBytesExt}, ParseError }; @@ -139,7 +172,6 @@ pub enum FirmwareResource<'data, E: EndianParse> { Unknown(u32), } -pub const FW_RSC_ADDR_ANY: u32 = u32::MAX; impl<'data, E: EndianParse> FirmwareResource<'data, E> { fn parse_at( From 5b4466189dd119993279e62ee54793a37a757c84 Mon Sep 17 00:00:00 2001 From: Keaton Clark Date: Mon, 4 Aug 2025 21:02:37 -0700 Subject: [PATCH 3/4] Add impl for ElfStream --- src/elf_bytes.rs | 4 ++- src/elf_stream.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 6fe2812..06d41c6 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -554,7 +554,9 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { /// Get the section data for a given [SectionHeader], and interpret it as a /// [ResourceTable](crate::resource_table::ResourceTable) /// - /// Returns a ParseError if the section is not of type [abi::SHT_PROGBITS] + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_PROGBITS](abi::SHT_PROGBITS). pub fn section_data_as_resource_table( &self, shdr: &SectionHeader, diff --git a/src/elf_stream.rs b/src/elf_stream.rs index 026c885..68f761a 100644 --- a/src/elf_stream.rs +++ b/src/elf_stream.rs @@ -13,6 +13,7 @@ use crate::gnu_symver::{ use crate::note::NoteIterator; use crate::parse::{ParseAt, ParseError}; use crate::relocation::{RelIterator, RelaIterator}; +use crate::resource_table::ResourceTable; use crate::section::{SectionHeader, SectionHeaderTable}; use crate::segment::ProgramHeader; use crate::segment::SegmentTable; @@ -646,6 +647,35 @@ impl ElfStream { )) } + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [ResourceTable](crate::resource_table::ResourceTable) + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_PROGBITS](abi::SHT_PROGBITS). + pub fn section_data_as_resource_table( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_PROGBITS { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_PROGBITS, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + let mut offset = 0; + ResourceTable::parse_at( + self.ehdr.endianness, + self.ehdr.class, + &mut offset, + buf, + ) + } + /// Read the segment data for the given [Segment](ProgramHeader). pub fn segment_data(&mut self, phdr: &ProgramHeader) -> Result<&[u8], ParseError> { let (start, end) = phdr.get_file_data_range()?; @@ -739,6 +769,7 @@ impl CachingReader { #[cfg(test)] mod interface_tests { use super::*; + use crate::abi::FW_RSC_ADDR_ANY; use crate::dynamic::Dyn; use crate::endian::AnyEndian; use crate::hash::SysVHashTable; @@ -1112,6 +1143,52 @@ mod interface_tests { assert!(notes.next().is_none()); } + #[test] + fn section_data_as_resource_table() { + use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring}; + let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdrs = file.section_headers(); + let shdr = shdrs[24]; + + let resource_table = file + .section_data_as_resource_table(&shdr) + .expect("Failed to read .resource_table section"); + + let mut it = (&resource_table).into_iter(); + if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { + assert_eq!(vdev.id, 7); + assert_eq!(vdev.notify_id, 0); + assert_eq!(vdev.dfeatures, 1); + assert_eq!(vdev.num_of_vrings, 2); + let mut it = (&vdev).into_iter(); + + let vdev_vring1 = it.next().unwrap(); + assert_eq!(vdev_vring1, + FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 1, pa: 0 } + ); + + let vdev_vring2 = it.next().unwrap(); + assert_eq!(vdev_vring2, + FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 2, pa: 0 } + ); + assert!(it.next().is_none()); + } else { + panic!(".resource_table parsed incorrectly"); + } + + if let FirmwareResource::Trace(trace) = it.next().unwrap() { + assert_eq!(trace.da, 0xA0106080); + assert_eq!(trace.len, 0x1000); + let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); + assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); + } else { + panic!(".resource_table parsed incorrectly"); + } + } + #[test] fn symbol_version_table() { let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); From 7a376eef30f40044706383394fc5074867eb5bb6 Mon Sep 17 00:00:00 2001 From: Keaton Clark Date: Wed, 6 Aug 2025 21:34:28 -0700 Subject: [PATCH 4/4] Misread the fw_rsc_vdev struct and forgot to run fmt --- src/abi.rs | 2 +- src/elf_bytes.rs | 48 ++++++++++++++------- src/elf_stream.rs | 42 +++++++++++++------ src/resource_table.rs | 97 +++++++++++++++++++++---------------------- 4 files changed, 110 insertions(+), 79 deletions(-) diff --git a/src/abi.rs b/src/abi.rs index bdfc93b..00d3020 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -3146,7 +3146,7 @@ pub const DT_MIPS_DIRECT: u32 = 0x70000032; /// The dynamic symbol entry of a callback function pub const DT_MIPS_RLD_OBJ_UPDATE: u32 = 0x70000033; -// resource_type in FirmwareResource +// resource_type in FirmwareResource /// Request for allocation of a physically contiguous memory region. pub const RSC_CARVEOUT: u32 = 0; /// Request to iommu_map a memory-based peripheral. diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 06d41c6..601a8f0 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -550,7 +550,6 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { )) } - /// Get the section data for a given [SectionHeader], and interpret it as a /// [ResourceTable](crate::resource_table::ResourceTable) /// @@ -570,12 +569,7 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { let (buf, _) = self.section_data(shdr)?; let mut offset = 0; - ResourceTable::parse_at( - self.ehdr.endianness, - self.ehdr.class, - &mut offset, - buf, - ) + ResourceTable::parse_at(self.ehdr.endianness, self.ehdr.class, &mut offset, buf) } /// Internal helper to get the section data for an SHT_DYNAMIC section as a .dynamic section table. @@ -857,7 +851,10 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { #[cfg(test)] mod interface_tests { use super::*; - use crate::abi::{FW_RSC_ADDR_ANY, SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB}; + use crate::abi::{ + FW_RSC_ADDR_ANY, SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, + SHT_STRTAB, + }; use crate::endian::AnyEndian; use crate::hash::sysv_hash; use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId}; @@ -1246,7 +1243,7 @@ mod interface_tests { let resource_table = file .section_data_as_resource_table(&shdr) .expect("Failed to read .resource_table section"); - + let mut it = (&resource_table).into_iter(); if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { assert_eq!(vdev.id, 7); @@ -1256,13 +1253,27 @@ mod interface_tests { let mut it = (&vdev).into_iter(); let vdev_vring1 = it.next().unwrap(); - assert_eq!(vdev_vring1, - FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 1, pa: 0 } + assert_eq!( + vdev_vring1, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 1, + pa: 0 + } ); let vdev_vring2 = it.next().unwrap(); - assert_eq!(vdev_vring2, - FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 2, pa: 0 } + assert_eq!( + vdev_vring2, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 2, + pa: 0 + } ); assert!(it.next().is_none()); } else { @@ -1272,8 +1283,15 @@ mod interface_tests { if let FirmwareResource::Trace(trace) = it.next().unwrap() { assert_eq!(trace.da, 0xA0106080); assert_eq!(trace.len, 0x1000); - let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); - assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); + let null_pos = trace + .name + .iter() + .position(|&c| c == 0) + .unwrap_or(trace.name.len()); + assert_eq!( + String::from_utf8_lossy(&trace.name[..null_pos]), + "trace:r5fss0_0" + ); } else { panic!(".resource_table parsed incorrectly"); } diff --git a/src/elf_stream.rs b/src/elf_stream.rs index 68f761a..70cd950 100644 --- a/src/elf_stream.rs +++ b/src/elf_stream.rs @@ -668,12 +668,7 @@ impl ElfStream { let (start, end) = shdr.get_data_range()?; let buf = self.reader.read_bytes(start, end)?; let mut offset = 0; - ResourceTable::parse_at( - self.ehdr.endianness, - self.ehdr.class, - &mut offset, - buf, - ) + ResourceTable::parse_at(self.ehdr.endianness, self.ehdr.class, &mut offset, buf) } /// Read the segment data for the given [Segment](ProgramHeader). @@ -1156,7 +1151,7 @@ mod interface_tests { let resource_table = file .section_data_as_resource_table(&shdr) .expect("Failed to read .resource_table section"); - + let mut it = (&resource_table).into_iter(); if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { assert_eq!(vdev.id, 7); @@ -1166,13 +1161,27 @@ mod interface_tests { let mut it = (&vdev).into_iter(); let vdev_vring1 = it.next().unwrap(); - assert_eq!(vdev_vring1, - FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 1, pa: 0 } + assert_eq!( + vdev_vring1, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 1, + pa: 0 + } ); let vdev_vring2 = it.next().unwrap(); - assert_eq!(vdev_vring2, - FirmwareResourceVdevVring { da: FW_RSC_ADDR_ANY, align: 0x1000, num: 256, notify_id: 2, pa: 0 } + assert_eq!( + vdev_vring2, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 2, + pa: 0 + } ); assert!(it.next().is_none()); } else { @@ -1182,8 +1191,15 @@ mod interface_tests { if let FirmwareResource::Trace(trace) = it.next().unwrap() { assert_eq!(trace.da, 0xA0106080); assert_eq!(trace.len, 0x1000); - let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); - assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); + let null_pos = trace + .name + .iter() + .position(|&c| c == 0) + .unwrap_or(trace.name.len()); + assert_eq!( + String::from_utf8_lossy(&trace.name[..null_pos]), + "trace:r5fss0_0" + ); } else { panic!(".resource_table parsed incorrectly"); } diff --git a/src/resource_table.rs b/src/resource_table.rs index 2209b93..5c5c414 100644 --- a/src/resource_table.rs +++ b/src/resource_table.rs @@ -32,7 +32,11 @@ //! } //! ``` use crate::{ - abi::{RSC_CARVEOUT, RSC_VDEV, RSC_TRACE, RSC_DEVMEM}, endian::EndianParse, file::Class, parse::{ParseAt, ReadBytesExt}, ParseError + abi::{RSC_CARVEOUT, RSC_DEVMEM, RSC_TRACE, RSC_VDEV}, + endian::EndianParse, + file::Class, + parse::{ParseAt, ReadBytesExt}, + ParseError, }; /// Firmware resource table header @@ -102,10 +106,7 @@ impl<'data, E: EndianParse> ResourceTable<'data, E> { } Ok(resource_table) } - fn get( - &self, - entry: usize, - ) -> Result, ParseError> { + fn get(&self, entry: usize) -> Result, ParseError> { if entry >= self.num_resources as usize { return Err(ParseError::BadOffset(entry as u64)); } @@ -114,11 +115,13 @@ impl<'data, E: EndianParse> ResourceTable<'data, E> { let mut resource_offsets_offset: usize = entry * size_of::(); // offset into the resource_entries - let mut resource_entry_offset = self.endian - .parse_u32_at(&mut resource_offsets_offset, self.resource_offsets)? as usize; + let mut resource_entry_offset = self + .endian + .parse_u32_at(&mut resource_offsets_offset, self.resource_offsets)? + as usize; // Subtract the header size - resource_entry_offset = resource_entry_offset - - ((size_of::() * 4) + self.resource_offsets.len()) as usize; + resource_entry_offset = + resource_entry_offset - ((size_of::() * 4) + self.resource_offsets.len()) as usize; FirmwareResource::parse_at( self.endian, @@ -127,9 +130,7 @@ impl<'data, E: EndianParse> ResourceTable<'data, E> { self.resource_entries, ) } - pub fn to_iter( - &'data self, - ) -> FirmwareResourceIterator<'data, E> { + pub fn to_iter(&'data self) -> FirmwareResourceIterator<'data, E> { FirmwareResourceIterator { idx: 0, resource_table: self, @@ -172,7 +173,6 @@ pub enum FirmwareResource<'data, E: EndianParse> { Unknown(u32), } - impl<'data, E: EndianParse> FirmwareResource<'data, E> { fn parse_at( endian: E, @@ -191,30 +191,21 @@ impl<'data, E: EndianParse> FirmwareResource<'data, E> { &data[..*offset + FirmwareResourceCarveout::size_for(class)], )?, )), - RSC_DEVMEM => Ok(FirmwareResource::Devmem( - FirmwareResourceDevmem::parse_at( - endian, - class, - offset, - &data[..*offset + FirmwareResourceDevmem::size_for(class)], - )? - )), - RSC_TRACE => Ok(FirmwareResource::Trace( - FirmwareResourceTrace::parse_at( - endian, - class, - offset, - &data[..*offset + FirmwareResourceTrace::size_for(class)], - )? - )), - RSC_VDEV => Ok(FirmwareResource::Vdev( - FirmwareResourceVdev::parse_at( - endian, - class, - offset, - &data, - )? - )), + RSC_DEVMEM => Ok(FirmwareResource::Devmem(FirmwareResourceDevmem::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceDevmem::size_for(class)], + )?)), + RSC_TRACE => Ok(FirmwareResource::Trace(FirmwareResourceTrace::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceTrace::size_for(class)], + )?)), + RSC_VDEV => Ok(FirmwareResource::Vdev(FirmwareResourceVdev::parse_at( + endian, class, offset, &data, + )?)), _ => Ok(FirmwareResource::Unknown(resource_type)), } } @@ -309,7 +300,6 @@ impl<'data> FirmwareResourceDevmem<'data> { } } - /// trace buffer declaration #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct FirmwareResourceTrace<'data> { @@ -424,11 +414,13 @@ pub struct FirmwareResourceVdev<'data, E: EndianParse> { /// negotiated features that are supported by both sides. pub gfeatures: u32, /// A place holder where the host will indicate its virtio progress. - pub status: u8, + pub status: u8, /// Indicates how many vrings are described in this vdev header pub num_of_vrings: u8, /// An array of num_of_vrings entries pub vrings: &'data [u8], + /// virtio config space for this vdev (which is specific to the vdev; for more info, read the virtio spec). + pub config: &'data [u8], /// Used to create the iterator later endian: E, @@ -452,7 +444,14 @@ impl<'data, E: EndianParse> FirmwareResourceVdev<'data, E> { let num_of_vrings = endian.parse_u8_at(offset, data)?; let _reserved = endian.parse_u8_at(offset, data)?; let _reserved = endian.parse_u8_at(offset, data)?; - let vrings = data.get_bytes(*offset..*offset + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)) as usize)?; + let vrings = data.get_bytes( + *offset + ..*offset + + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)) + as usize, + )?; + *offset = *offset + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)); + let config = data.get_bytes(*offset..*offset + config_len as usize)?; *offset = *offset + config_len as usize; Ok(FirmwareResourceVdev { id, @@ -462,22 +461,18 @@ impl<'data, E: EndianParse> FirmwareResourceVdev<'data, E> { num_of_vrings, status, vrings, + config, endian, class, }) } - pub fn to_iter( - &'data self, - ) -> FirmwareResourceVdevVringIterator<'data, E> { + pub fn to_iter(&'data self) -> FirmwareResourceVdevVringIterator<'data, E> { FirmwareResourceVdevVringIterator { idx: 0, firmware_resource_vdev: self, } } - fn get( - &self, - entry: usize, - ) -> Result { + fn get(&self, entry: usize) -> Result { if entry >= self.num_of_vrings as usize { return Err(ParseError::BadOffset(entry as u64)); } @@ -487,7 +482,8 @@ impl<'data, E: EndianParse> FirmwareResourceVdev<'data, E> { self.endian, self.class, &mut offset, - &self.vrings[entry * FirmwareResourceVdevVring::size_for(self.class)..(entry + 1) * FirmwareResourceVdevVring::size_for(self.class)], + &self.vrings[entry * FirmwareResourceVdevVring::size_for(self.class) + ..(entry + 1) * FirmwareResourceVdevVring::size_for(self.class)], ) } } @@ -596,8 +592,9 @@ mod parse_tests { ]; let mut offset = 0; - let resource_table = ResourceTable::parse_at(LittleEndian, Class::ELF32, &mut offset, &data) - .expect("Failed to parse"); + let resource_table = + ResourceTable::parse_at(LittleEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); assert_eq!(resource_table.version, 1); assert_eq!(resource_table.num_resources, 1);