diff --git a/crates/examples/testfiles/wasm/base.o.objdump b/crates/examples/testfiles/wasm/base.o.objdump index c749feed..93550b45 100644 --- a/crates/examples/testfiles/wasm/base.o.objdump +++ b/crates/examples/testfiles/wasm/base.o.objdump @@ -4,6 +4,7 @@ Architecture: Wasm32 Flags: None Relative Address Base: 0 Entry Address: 0 +Segment { name: ".rodata..L.str", address: 0, size: d, permissions: RW- } 1: Section { name: "", address: 0, size: b, align: 1, kind: Metadata, flags: None } 2: Section { name: "", address: 0, size: 5e, align: 1, kind: Linker, flags: None } 3: Section { name: "", address: 0, size: 3, align: 1, kind: Metadata, flags: None } diff --git a/crates/examples/testfiles/wasm/base.wasm.objdump b/crates/examples/testfiles/wasm/base.wasm.objdump index 460411dc..5f41dac1 100644 --- a/crates/examples/testfiles/wasm/base.wasm.objdump +++ b/crates/examples/testfiles/wasm/base.wasm.objdump @@ -4,6 +4,8 @@ Architecture: Wasm32 Flags: None Relative Address Base: 0 Entry Address: 0 +Segment { address: 400, size: 965, permissions: RW- } +Segment { address: d68, size: ec, permissions: RW- } 1: Section { name: "", address: 0, size: 4a, align: 1, kind: Metadata, flags: None } 2: Section { name: "", address: 0, size: b0, align: 1, kind: Linker, flags: None } 3: Section { name: "", address: 0, size: 25, align: 1, kind: Metadata, flags: None } diff --git a/crates/examples/testfiles/wasm/global-wasm32.objdump b/crates/examples/testfiles/wasm/global-wasm32.objdump index da821aa6..3f07a085 100644 --- a/crates/examples/testfiles/wasm/global-wasm32.objdump +++ b/crates/examples/testfiles/wasm/global-wasm32.objdump @@ -4,6 +4,7 @@ Architecture: Wasm32 Flags: None Relative Address Base: 0 Entry Address: 0 +Segment { address: 400, size: 8, permissions: RW- } 1: Section { name: "", address: 0, size: 8, align: 1, kind: Metadata, flags: None } 2: Section { name: "", address: 0, size: 13, align: 1, kind: Linker, flags: None } 3: Section { name: "", address: 0, size: 3, align: 1, kind: Metadata, flags: None } diff --git a/crates/examples/testfiles/wasm/global-wasm64-import.objdump b/crates/examples/testfiles/wasm/global-wasm64-import.objdump index c79a84ec..746fb033 100644 --- a/crates/examples/testfiles/wasm/global-wasm64-import.objdump +++ b/crates/examples/testfiles/wasm/global-wasm64-import.objdump @@ -4,6 +4,7 @@ Architecture: Wasm64 Flags: None Relative Address Base: 0 Entry Address: 0 +Segment { address: 400, size: 8, permissions: RW- } 1: Section { name: "", address: 0, size: 8, align: 1, kind: Metadata, flags: None } 2: Section { name: "", address: 0, size: 21, align: 1, kind: Linker, flags: None } 3: Section { name: "", address: 0, size: 3, align: 1, kind: Metadata, flags: None } diff --git a/crates/examples/testfiles/wasm/global-wasm64.objdump b/crates/examples/testfiles/wasm/global-wasm64.objdump index 38ec9449..0db75acf 100644 --- a/crates/examples/testfiles/wasm/global-wasm64.objdump +++ b/crates/examples/testfiles/wasm/global-wasm64.objdump @@ -4,6 +4,7 @@ Architecture: Wasm64 Flags: None Relative Address Base: 0 Entry Address: 0 +Segment { address: 400, size: 8, permissions: RW- } 1: Section { name: "", address: 0, size: 8, align: 1, kind: Metadata, flags: None } 2: Section { name: "", address: 0, size: 13, align: 1, kind: Linker, flags: None } 3: Section { name: "", address: 0, size: 3, align: 1, kind: Metadata, flags: None } diff --git a/src/common.rs b/src/common.rs index 9f8a6f45..0c2c8fc4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -535,6 +535,13 @@ pub enum SegmentFlags { /// `Characteristics` field in the segment header. characteristics: crate::pe::SectionFlags, }, + /// Wasm segment flags. + #[cfg(feature = "wasm")] + Wasm { + /// The raw `flags` bits as defined by the WebAssembly tool conventions. + /// See . + flags: u32, + }, } /// Memory permissions for a segment. diff --git a/src/read/wasm.rs b/src/read/wasm.rs index 0c68834f..dce4f9f5 100644 --- a/src/read/wasm.rs +++ b/src/read/wasm.rs @@ -50,6 +50,8 @@ pub struct WasmFile<'data, R = &'data [u8]> { id_sections: Box<[Option; MAX_SECTION_ID + 1]>, // Parsed `reloc.*` custom sections, keyed by the binary index of the target section. relocations: Vec, + // Data segments parsed from the `data` section. + data_segments: Vec>, // Whether the file has DWARF information. has_debug_symbols: bool, // Symbols collected from imports, exports, code and name sections. @@ -65,6 +67,26 @@ struct RelocSection { entries: Vec, } +#[derive(Debug)] +struct WasmDataSegmentInternal<'data> { + /// Name from the `SegmentInfo` subsection of `linking`. Empty if unavailable. + name: &'data str, + /// Required alignment encoded as a power of 2. + alignment: u32, + /// Raw `SegmentFlags` bits from `SegmentInfo`. + flags: u32, + /// Whether `SegmentInfo` provided metadata for this segment. + has_info: bool, + /// The address of the segment in linear memory (for active segments), or 0. + address: u64, + /// Whether this is a passive (non-active) data segment. + is_passive: bool, + /// File offset of `data` within the wasm module. + file_offset: u64, + /// Raw bytes of the segment. + data: &'data [u8], +} + #[derive(Debug)] struct SectionHeader<'data> { id: SectionId, @@ -98,6 +120,7 @@ impl<'data, R: ReadRef<'data>> WasmFile<'data, R> { sections: Vec::new(), id_sections: Default::default(), relocations: Vec::new(), + data_segments: Vec::new(), has_debug_symbols: false, symbols: Vec::new(), entry: 0, @@ -122,6 +145,7 @@ impl<'data, R: ReadRef<'data>> WasmFile<'data, R> { let mut exports = None; let mut names = None; let mut symbols = None; + let mut segment_infos: Vec> = Vec::new(); // One-to-one mapping of globals to their value (if the global is a constant integer). let mut global_values = Vec::new(); @@ -196,6 +220,40 @@ impl<'data, R: ReadRef<'data>> WasmFile<'data, R> { } wp::Payload::DataSection(section) => { file.add_section(SectionId::Data, section.range(), ""); + for segment in section.clone() { + let segment = segment.read_error("Couldn't read a data segment")?; + let mut address = 0u64; + let mut is_passive = false; + match &segment.kind { + wp::DataKind::Active { offset_expr, .. } => { + let init = offset_expr.get_operators_reader().read(); + address = match init + .read_error("Couldn't read a data segment offset expr")? + { + wp::Operator::I32Const { value } => value as u32 as u64, + wp::Operator::I64Const { value } => value as u64, + _ => 0, + }; + } + wp::DataKind::Passive => { + is_passive = true; + } + } + // Compute file offset of `segment.data` within the wasm module. + let file_offset = (segment.data.as_ptr() as usize) + .wrapping_sub(data.as_ptr() as usize) + as u64; + file.data_segments.push(WasmDataSegmentInternal { + name: "", + alignment: 0, + flags: 0, + has_info: false, + address, + is_passive, + file_offset, + data: segment.data, + }); + } } wp::Payload::DataCountSection { range, .. } => { file.add_section(SectionId::DataCount, range, ""); @@ -220,8 +278,18 @@ impl<'data, R: ReadRef<'data>> WasmFile<'data, R> { for subsection in linking { let subsection = subsection.read_error("Invalid Wasm linking subsection")?; - if let wp::Linking::SymbolTable(s) = subsection { - symbols = Some(s); + match subsection { + wp::Linking::SymbolTable(s) => { + symbols = Some(s); + } + wp::Linking::SegmentInfo(map) => { + for segment in map { + let segment = segment + .read_error("Invalid Wasm linking SegmentInfo entry")?; + segment_infos.push(segment); + } + } + _ => {} } } } else if name.strip_prefix("reloc.").is_some() { @@ -250,6 +318,16 @@ impl<'data, R: ReadRef<'data>> WasmFile<'data, R> { } } + // Apply any `SegmentInfo` entries collected from the `linking` section to the already-parsed data segments. + for (i, info) in segment_infos.into_iter().enumerate() { + if let Some(slot) = file.data_segments.get_mut(i) { + slot.name = info.name; + slot.alignment = info.alignment; + slot.flags = info.flags.bits(); + slot.has_info = true; + } + } + let mut import_func_names = Vec::new(); let mut import_global_names = Vec::new(); if let Some(imports_section) = imports_section { @@ -623,7 +701,10 @@ impl<'data, R: ReadRef<'data>> Object<'data> for WasmFile<'data, R> { } fn segments(&self) -> Self::SegmentIterator<'_> { - WasmSegmentIterator { file: self } + WasmSegmentIterator { + file: self, + iter: self.data_segments.iter().enumerate(), + } } fn section_by_name_bytes<'file>( @@ -726,12 +807,10 @@ impl<'data, R: ReadRef<'data>> Object<'data> for WasmFile<'data, R> { } /// An iterator for the segments in a [`WasmFile`]. -/// -/// This is a stub that doesn't implement any functionality. #[derive(Debug)] pub struct WasmSegmentIterator<'data, 'file, R = &'data [u8]> { - #[allow(unused)] file: &'file WasmFile<'data, R>, + iter: core::iter::Enumerate>>, } impl<'data, 'file, R> Iterator for WasmSegmentIterator<'data, 'file, R> { @@ -739,17 +818,23 @@ impl<'data, 'file, R> Iterator for WasmSegmentIterator<'data, 'file, R> { #[inline] fn next(&mut self) -> Option { - None + let (index, segment) = self.iter.next()?; + Some(WasmSegment { + file: self.file, + index, + segment, + }) } } /// A segment in a [`WasmFile`]. -/// -/// This is a stub that doesn't implement any functionality. #[derive(Debug)] pub struct WasmSegment<'data, 'file, R = &'data [u8]> { #[allow(unused)] file: &'file WasmFile<'data, R>, + #[allow(unused)] + index: usize, + segment: &'file WasmDataSegmentInternal<'data>, } impl<'data, 'file, R> read::private::Sealed for WasmSegment<'data, 'file, R> {} @@ -757,50 +842,82 @@ impl<'data, 'file, R> read::private::Sealed for WasmSegment<'data, 'file, R> {} impl<'data, 'file, R> ObjectSegment<'data> for WasmSegment<'data, 'file, R> { #[inline] fn address(&self) -> u64 { - unreachable!() + self.segment.address } #[inline] fn size(&self) -> u64 { - unreachable!() + self.segment.data.len() as u64 } #[inline] fn align(&self) -> u64 { - unreachable!() + // `alignment` is encoded as a power of 2. + if self.segment.has_info { + 1u64 << self.segment.alignment + } else { + 1 + } } #[inline] fn file_range(&self) -> (u64, u64) { - unreachable!() + (self.segment.file_offset, self.segment.data.len() as u64) } fn data(&self) -> Result<&'data [u8]> { - unreachable!() + Ok(self.segment.data) } - fn data_range(&self, _address: u64, _size: u64) -> Result> { - unreachable!() + fn data_range(&self, address: u64, size: u64) -> Result> { + let segment_address = self.segment.address; + let segment_size = self.segment.data.len() as u64; + if self.segment.is_passive { + return Ok(None); + } + if address < segment_address { + return Ok(None); + } + let offset = address - segment_address; + if offset + .checked_add(size) + .map_or(true, |end| end > segment_size) + { + return Ok(None); + } + let offset = offset as usize; + let size = size as usize; + Ok(Some(&self.segment.data[offset..offset + size])) } #[inline] fn name_bytes(&self) -> Result> { - unreachable!() + if self.segment.has_info { + Ok(Some(self.segment.name.as_bytes())) + } else { + Ok(None) + } } #[inline] fn name(&self) -> Result> { - unreachable!() + if self.segment.has_info { + Ok(Some(self.segment.name)) + } else { + Ok(None) + } } #[inline] fn flags(&self) -> SegmentFlags { - unreachable!() + SegmentFlags::Wasm { + flags: self.segment.flags, + } } #[inline] fn permissions(&self) -> Permissions { - unreachable!() + Permissions::new(true, true, false) } }