From ebd9c41b4a87fb7f37912ebeeacbfdaa5702edce Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 11 Jun 2026 10:42:04 -0600 Subject: [PATCH] Rework OMF sections to include COMDATs; fix Borland/Watcom parsing - Fix SEGDEF length field size to depend on record type, not the P (use32) bit, and honor the B (big) bit. This fixes parsing of Borland objects. - Parse COMDAT records per the TIS spec (separate align byte, conditional public base fields), synthesize sections and symbols for them, and support continuation and iterated data. - Allow FIXUPP records to follow COMDAT records. - Support Borland virtual segments (COMDEF with a segment index data type, referenced via segment indices with bit 14 set). - Read the FIXUP M bit from the correct byte (LOCAT instead of fix data). - Fix iterated data (LIDATA) repeat/block counts to be plain integers rather than COMDEF-style encoded values, support multiple consecutive blocks, and propagate expansion errors. - Fix relocation section targets to use 1-based section indices. - Rework relocation kind mapping: self-relative fixups map to Relative with an end-of-location addend; segment-relative offsets map to Absolute (FLAT frame) or SectionOffset (target-section frame). - Return empty imports/exports like other relocatable formats. --- src/read/omf/comdat.rs | 65 +-- src/read/omf/file.rs | 1130 ++++++++++++++++++++---------------- src/read/omf/relocation.rs | 168 +++--- src/read/omf/section.rs | 139 +---- src/read/omf/segment.rs | 234 ++++++-- src/read/omf/symbol.rs | 131 +++-- 6 files changed, 1033 insertions(+), 834 deletions(-) diff --git a/src/read/omf/comdat.rs b/src/read/omf/comdat.rs index 3d01853..6736263 100644 --- a/src/read/omf/comdat.rs +++ b/src/read/omf/comdat.rs @@ -1,44 +1,41 @@ use crate::read::{self, Error, Result}; -use crate::{omf, ComdatKind, ObjectComdat, ReadRef, SectionIndex, SymbolIndex}; +use crate::{ComdatKind, ObjectComdat, ReadRef, SectionIndex, SymbolIndex}; use super::OmfFile; -/// A COMDAT (communal data) section +/// Internal representation of a COMDAT. #[derive(Debug, Clone)] pub(super) struct OmfComdatData<'data> { /// Symbol name pub(super) name: &'data [u8], - /// Segment index where this COMDAT belongs - pub(super) segment_index: u16, - /// Selection/allocation method + /// Index of the synthesized section (0-based index into `sections`) + pub(super) section: usize, + /// Index of the synthesized symbol + pub(super) symbol: SymbolIndex, + /// Selection criteria pub(super) selection: OmfComdatSelection, - /// Alignment - #[allow(unused)] - pub(super) alignment: omf::SegmentAlignment, - /// Data - #[allow(unused)] - pub(super) data: &'data [u8], } -/// COMDAT selection methods +/// COMDAT selection criteria #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) enum OmfComdatSelection { /// Explicit: may not be combined, produce error if multiple definitions - Explicit = 0, + Explicit, /// Use any: pick any instance - UseAny = 1, + UseAny, /// Same size: all instances must be same size - SameSize = 2, + SameSize, /// Exact match: all instances must have identical content - ExactMatch = 3, + ExactMatch, } -/// A COMDAT section in an OMF file. +/// A COMDAT section group in an [`OmfFile`]. +/// +/// Most functionality is provided by the [`ObjectComdat`] trait implementation. #[derive(Debug)] pub struct OmfComdat<'data, 'file, R: ReadRef<'data>> { file: &'file OmfFile<'data, R>, index: usize, - _phantom: core::marker::PhantomData<&'data ()>, } impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for OmfComdat<'data, 'file, R> {} @@ -47,8 +44,7 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectComdat<'data> for OmfComdat<'data, ' type SectionIterator = OmfComdatSectionIterator<'data, 'file, R>; fn kind(&self) -> ComdatKind { - let comdat = &self.file.comdats[self.index]; - match comdat.selection { + match self.file.comdats[self.index].selection { OmfComdatSelection::Explicit => ComdatKind::NoDuplicates, OmfComdatSelection::UseAny => ComdatKind::Any, OmfComdatSelection::SameSize => ComdatKind::SameSize, @@ -57,31 +53,27 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectComdat<'data> for OmfComdat<'data, ' } fn symbol(&self) -> SymbolIndex { - // COMDAT symbols don't have a direct symbol index in OMF - SymbolIndex(usize::MAX) + self.file.comdats[self.index].symbol } fn name_bytes(&self) -> Result<&'data [u8]> { - let comdat = &self.file.comdats[self.index]; - Ok(comdat.name) + Ok(self.file.comdats[self.index].name) } fn name(&self) -> Result<&'data str> { - let comdat = &self.file.comdats[self.index]; - core::str::from_utf8(comdat.name).map_err(|_| Error("Invalid UTF-8 in COMDAT name")) + core::str::from_utf8(self.file.comdats[self.index].name) + .map_err(|_| Error("Invalid UTF-8 in COMDAT name")) } fn sections(&self) -> Self::SectionIterator { - let comdat = &self.file.comdats[self.index]; OmfComdatSectionIterator { - segment_index: (comdat.segment_index as usize).checked_sub(1), - returned: false, + section: Some(SectionIndex(self.file.comdats[self.index].section + 1)), _phantom: core::marker::PhantomData, } } } -/// An iterator over COMDAT sections. +/// An iterator for the COMDAT section groups in an [`OmfFile`]. #[derive(Debug)] pub struct OmfComdatIterator<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>, @@ -96,7 +88,6 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfComdatIterator<'data, 'fil let comdat = OmfComdat { file: self.file, index: self.index, - _phantom: core::marker::PhantomData, }; self.index += 1; Some(comdat) @@ -106,11 +97,10 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfComdatIterator<'data, 'fil } } -/// An iterator over sections in a COMDAT. +/// An iterator for the sections in a [`OmfComdat`]. #[derive(Debug)] pub struct OmfComdatSectionIterator<'data, 'file, R: ReadRef<'data>> { - segment_index: Option, - returned: bool, + section: Option, _phantom: core::marker::PhantomData<(&'data (), &'file (), R)>, } @@ -118,11 +108,6 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfComdatSectionIterator<'dat type Item = SectionIndex; fn next(&mut self) -> Option { - if !self.returned { - self.returned = true; - self.segment_index.map(|idx| SectionIndex(idx + 1)) - } else { - None - } + self.section.take() } } diff --git a/src/read/omf/file.rs b/src/read/omf/file.rs index cae0705..983b6cb 100644 --- a/src/read/omf/file.rs +++ b/src/read/omf/file.rs @@ -3,9 +3,9 @@ use alloc::vec::Vec; use crate::read::{ - self, Architecture, ByteString, CodeView, Error, Export, FileFlags, Import, - NoDynamicRelocationIterator, Object, ObjectKind, ObjectSection, ReadRef, Result, SectionIndex, - SymbolIndex, + self, Architecture, CodeView, Error, Export, FileFlags, Import, NoDynamicRelocationIterator, + Object, ObjectKind, ObjectSection, ReadRef, Result, SectionIndex, SectionKind, SymbolIndex, + SymbolKind, }; use crate::{omf, SubArchitecture}; @@ -18,13 +18,22 @@ use super::{ /// An OMF object file. /// /// This handles both 16-bit and 32-bit OMF variants. +/// +/// OMF doesn't have a notion of sections, so this implementation maps both +/// segments (`SEGDEF`) and COMDATs (`COMDAT`) to sections in the unified API. #[derive(Debug)] pub struct OmfFile<'data, R: ReadRef<'data> = &'data [u8]> { pub(super) data: R, /// The module name from THEADR/LHEADR record pub(super) module_name: Option<&'data str>, - /// Segment definitions - pub(super) segments: Vec>, + /// Sections, in record order. This contains an entry for each SEGDEF + /// record, as well as a synthesized entry for each COMDAT and each + /// Borland virtual segment (COMDEF). + pub(super) sections: Vec>, + /// Maps SEGDEF index (0-based) to an index into `sections`. + pub(super) segdefs: Vec, + /// Maps Borland virtual segment index (0-based) to an index into `sections`. + pub(super) virtual_segdefs: Vec, /// All symbols (publics, externals, communals, locals) in occurrence order pub(super) symbols: Vec>, /// Maps external-name table index (1-based) to SymbolIndex @@ -45,7 +54,9 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let mut file = OmfFile { data, module_name: None, - segments: Vec::new(), + sections: Vec::new(), + segdefs: Vec::new(), + virtual_segdefs: Vec::new(), symbols: Vec::new(), external_order: Vec::new(), comdats: Vec::new(), @@ -54,85 +65,118 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { }; file.parse_records()?; - file.assign_symbol_kinds(); + file.finish_symbols(); Ok(file) } - fn assign_symbol_kinds(&mut self) { - // Compute kinds for symbols based on their segments - let kinds: Vec = self + /// Compute symbol kinds and sizes that depend on the complete section list. + fn finish_symbols(&mut self) { + let kinds_and_sizes: Vec<(SymbolKind, u64)> = self .symbols .iter() - .map(|sym| match sym.class { - OmfSymbolClass::Public | OmfSymbolClass::LocalPublic => { - if sym.segment_index > 0 && (sym.segment_index as usize) <= self.segments.len() - { - let segment_idx = (sym.segment_index - 1) as usize; - let section_kind = self.segment_section_kind(segment_idx); - Self::symbol_kind_from_section_kind(section_kind) - } else { - read::SymbolKind::Unknown - } - } - OmfSymbolClass::Communal | OmfSymbolClass::LocalCommunal => read::SymbolKind::Data, - _ => read::SymbolKind::Unknown, + .map(|sym| { + let kind = if let Some(section) = sym.section { + Self::symbol_kind_from_section_kind( + self.section_kind(section.0.wrapping_sub(1)), + ) + } else if matches!( + sym.class, + OmfSymbolClass::Communal | OmfSymbolClass::LocalCommunal + ) { + SymbolKind::Data + } else { + SymbolKind::Unknown + }; + let size = match sym.class { + // The size of a COMDAT symbol is the size of its section. + OmfSymbolClass::Comdat | OmfSymbolClass::LocalComdat => sym + .section + .and_then(|section| self.sections.get(section.0.wrapping_sub(1))) + .map_or(0, |section| section.length), + _ => sym.size, + }; + (kind, size) }) .collect(); - // Apply computed kinds - for (sym, kind) in self.symbols.iter_mut().zip(kinds) { + for (sym, (kind, size)) in self.symbols.iter_mut().zip(kinds_and_sizes) { sym.kind = kind; + sym.size = size; } } - fn symbol_kind_from_section_kind(section_kind: read::SectionKind) -> read::SymbolKind { + fn symbol_kind_from_section_kind(section_kind: SectionKind) -> SymbolKind { match section_kind { - read::SectionKind::Text => read::SymbolKind::Text, - read::SectionKind::Data | read::SectionKind::ReadOnlyData => read::SymbolKind::Data, - read::SectionKind::UninitializedData => read::SymbolKind::Data, - _ => read::SymbolKind::Unknown, + SectionKind::Text => SymbolKind::Text, + SectionKind::Data + | SectionKind::ReadOnlyData + | SectionKind::UninitializedData + | SectionKind::Common => SymbolKind::Data, + _ => SymbolKind::Unknown, } } - /// Get the section kind for a segment - pub(super) fn segment_section_kind(&self, segment_index: usize) -> read::SectionKind { - let Some(segment) = self.segments.get(segment_index) else { - return read::SectionKind::Unknown; + /// Get the section kind for a section (0-based index into `sections`). + pub(super) fn section_kind(&self, section_index: usize) -> SectionKind { + let Some(section) = self.sections.get(section_index) else { + return SectionKind::Unknown; }; - let segment_name = self.get_name(segment.name_index).unwrap_or_default(); - let class_name = self.get_name(segment.class_index).unwrap_or_default(); + // COMDAT sections may have a kind determined by their allocation type. + if let Some(kind) = section.kind { + return kind; + } + + let section_name = section.name; + let class_name = section.class; // Reserved names for debug sections - if segment_name.starts_with(b"$$") { - return read::SectionKind::Debug; + if section_name.starts_with(b"$$") { + return SectionKind::Debug; } // Substring matches for common class names if class_name.windows(4).any(|w| w == b"CODE") { - return read::SectionKind::Text; + SectionKind::Text } else if class_name.windows(4).any(|w| w == b"DATA") { - if segment_name.windows(5).any(|w| w == b"CONST") { - return read::SectionKind::ReadOnlyData; + if section_name.windows(5).any(|w| w == b"CONST") { + SectionKind::ReadOnlyData } else { - return read::SectionKind::Data; + SectionKind::Data } } else if class_name.windows(3).any(|w| w == b"BSS") || class_name.windows(5).any(|w| w == b"STACK") { - return read::SectionKind::UninitializedData; + SectionKind::UninitializedData } else if class_name.starts_with(b"DEB") { - return read::SectionKind::Debug; + SectionKind::Debug } else if class_name == b"COMMON" { - return read::SectionKind::Common; + SectionKind::Common + } else { + SectionKind::Unknown } + } - read::SectionKind::Unknown + /// Translate a 1-based segment index into a 0-based index into `sections`. + /// + /// Indices with bit 14 set are a Borland extension that references + /// virtual segments defined by COMDEF records. + pub(super) fn segdef_section(&self, segment_index: u16) -> Result { + let (index, segdefs) = if segment_index & 0x4000 != 0 { + (segment_index & !0x4000, &self.virtual_segdefs) + } else { + (segment_index, &self.segdefs) + }; + index + .checked_sub(1) + .and_then(|i| segdefs.get(i as usize).copied()) + .ok_or(Error("Invalid OMF segment index")) } fn parse_records(&mut self) -> Result<()> { - let mut current_segment: Option = None; - let mut current_data_offset: Option = None; + // The data record (LEDATA/LIDATA/COMDAT) that a FIXUPP record applies to: + // a 0-based index into `sections` and the base data offset within it. + let mut last_data: Option<(usize, u32)> = None; // Thread storage for FIXUPP parsing let mut frame_threads: [Option; 4] = [None; 4]; @@ -201,7 +245,7 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { self.parse_extdef(inner_data, OmfSymbolClass::LocalExternal)?; } omf::record_type::CEXTDEF => { - self.parse_extdef(inner_data, OmfSymbolClass::ComdatExternal)?; + self.parse_cextdef(inner_data)?; } omf::record_type::COMDEF => { self.parse_comdef(inner_data, OmfSymbolClass::Communal)?; @@ -210,40 +254,28 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { self.parse_comdef(inner_data, OmfSymbolClass::LocalCommunal)?; } omf::record_type::COMDAT | omf::record_type::COMDAT32 => { - self.parse_comdat(inner_data, record_type == omf::record_type::COMDAT32)?; - } - omf::record_type::COMENT => { - self.parse_comment(inner_data)?; + let target = + self.parse_comdat(inner_data, record_type == omf::record_type::COMDAT32)?; + last_data = Some(target); } omf::record_type::LEDATA | omf::record_type::LEDATA32 => { - let (seg_idx, offset) = + let target = self.parse_ledata(inner_data, record_type == omf::record_type::LEDATA32)?; - current_segment = Some(seg_idx); - current_data_offset = Some(offset); + last_data = Some(target); } omf::record_type::LIDATA | omf::record_type::LIDATA32 => { - let (seg_idx, offset) = + let target = self.parse_lidata(inner_data, record_type == omf::record_type::LIDATA32)?; - current_segment = Some(seg_idx); - current_data_offset = Some(offset); + last_data = Some(target); } omf::record_type::FIXUPP | omf::record_type::FIXUPP32 => { - if let (Some(seg_idx), Some(data_offset)) = - (current_segment, current_data_offset) - { - self.parse_fixupp( - inner_data, - record_type == omf::record_type::FIXUPP32, - seg_idx, - data_offset, - &mut frame_threads, - &mut target_threads, - )?; - } else { - return Err(Error( - "FIXUPP/FIXUPP32 record encountered without preceding LEDATA/LIDATA", - )); - } + self.parse_fixupp( + inner_data, + record_type == omf::record_type::FIXUPP32, + last_data, + &mut frame_threads, + &mut target_threads, + )?; } omf::record_type::MODEND | omf::record_type::MODEND32 => { // End of module @@ -307,12 +339,13 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let combination = match (acbp >> 2) & 0x07 { 0 => omf::SegmentCombination::Private, - 2 => omf::SegmentCombination::Public, + 2 | 4 | 7 => omf::SegmentCombination::Public, 5 => omf::SegmentCombination::Stack, 6 => omf::SegmentCombination::Common, _ => return Err(Error("Invalid segment combination")), }; + let big = (acbp & 0x02) != 0; let use32 = (acbp & 0x01) != 0; // Skip frame number and offset for absolute segments @@ -320,8 +353,10 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { offset += 3; // frame (2) + offset (1) } - // Parse segment length - let length = if is_32bit || use32 { + // Parse segment length. The size of this field is determined by the + // record type alone; the P (use32) bit describes the default operand + // size of the segment, not the record layout. + let mut length = if is_32bit { if offset + 4 > data.len() { return Err(Error("Truncated SEGDEF record")); } @@ -330,17 +365,21 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { data[offset + 1], data[offset + 2], data[offset + 3], - ]); + ]) as u64; offset += 4; length } else { if offset + 2 > data.len() { return Err(Error("Truncated SEGDEF record")); } - let length = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; + let length = u16::from_le_bytes([data[offset], data[offset + 1]]) as u64; offset += 2; length }; + // The B bit indicates a segment of the maximum size (64K, or 4G for SEGDEF32). + if big && length == 0 { + length = if is_32bit { 1 << 32 } else { 1 << 16 }; + } // Parse segment name index let (name_index, size) = @@ -356,7 +395,10 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let (overlay_index, _) = read_index(&data[offset..]).ok_or(Error("Invalid overlay name index"))?; - self.segments.push(OmfSegment { + self.segdefs.push(self.sections.len()); + self.sections.push(OmfSegment { + name: self.get_name(name_index).unwrap_or_default(), + class: self.get_name(class_index).unwrap_or_default(), name_index, class_index, overlay_index, @@ -364,6 +406,8 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { combination, use32, length, + kind: None, + comdat: false, data_chunks: Vec::new(), relocations: Vec::new(), }); @@ -411,7 +455,7 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let mut offset = 0; // Parse group index - let (group_index, size) = read_index(data).ok_or(Error("Invalid group index"))?; + let (_group_index, size) = read_index(data).ok_or(Error("Invalid group index"))?; offset += size; // Parse segment index @@ -431,6 +475,12 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { 0 }; + let section = if segment_index == 0 { + None + } else { + Some(SectionIndex(self.segdef_section(segment_index)? + 1)) + }; + // Parse public definitions while offset < data.len() { // Parse name @@ -467,15 +517,16 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { offset += size; self.symbols.push(OmfSymbol { - symbol_index: self.symbols.len(), + index: SymbolIndex(self.symbols.len()), name, class, - group_index, - segment_index, + section, + absolute: segment_index == 0, frame_number, - offset: pub_offset, + offset: pub_offset as u64, + size: 0, type_index, - kind: read::SymbolKind::Unknown, // Will be computed later + kind: SymbolKind::Unknown, // Will be computed later }); } @@ -494,29 +545,57 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { // Parse type index let (type_index, size) = read_index(&data[offset..]) - .ok_or(Error("Invalid type index in EXTDEF/LEXTDEF/CEXTDEF record"))?; + .ok_or(Error("Invalid type index in EXTDEF/LEXTDEF record"))?; offset += size; - let sym_idx = self.symbols.len(); - self.symbols.push(OmfSymbol { - symbol_index: sym_idx, - name, - class, - group_index: 0, - segment_index: 0, - frame_number: 0, - offset: 0, - type_index, - kind: read::SymbolKind::Unknown, - }); - - // Add to external_order for symbols that contribute to external-name table - self.external_order.push(read::SymbolIndex(sym_idx)); + self.push_external(name, class, type_index); } Ok(()) } + /// Parse a CEXTDEF record, which references COMDAT symbols by name index. + fn parse_cextdef(&mut self, data: &'data [u8]) -> Result<()> { + let mut offset = 0; + + while offset < data.len() { + // Parse name index (into LNAMES) + let (name_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid name index in CEXTDEF record"))?; + offset += size; + + // Parse type index + let (type_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid type index in CEXTDEF record"))?; + offset += size; + + let name = self + .get_name(name_index) + .ok_or(Error("Invalid name index in CEXTDEF record"))?; + self.push_external(name, OmfSymbolClass::ComdatExternal, type_index); + } + + Ok(()) + } + + /// Add a symbol that contributes to the external-name table. + fn push_external(&mut self, name: &'data [u8], class: OmfSymbolClass, type_index: u16) { + let index = SymbolIndex(self.symbols.len()); + self.symbols.push(OmfSymbol { + index, + name, + class, + section: None, + absolute: false, + frame_number: 0, + offset: 0, + size: 0, + type_index, + kind: SymbolKind::Unknown, + }); + self.external_order.push(index); + } + fn parse_comdef(&mut self, data: &'data [u8], class: OmfSymbolClass) -> Result<()> { let mut offset = 0; @@ -539,58 +618,117 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let data_type = data[offset]; offset += 1; - let communal_length = match data_type { + let (communal_length, base_segment) = match data_type { 0x61 => { // FAR data - number of elements followed by element size - let (num_elements, size1) = read_encoded_value(&data[offset..]) + let (num_elements, size1) = read_communal_length(&data[offset..]) .ok_or(Error("Invalid number of elements in FAR COMDEF"))?; offset += size1; - let (element_size, size2) = read_encoded_value(&data[offset..]) + let (element_size, size2) = read_communal_length(&data[offset..]) .ok_or(Error("Invalid element size in FAR COMDEF"))?; offset += size2; - num_elements * element_size + ((num_elements as u64) * (element_size as u64), None) } 0x62 => { // NEAR data - size in bytes - let (size_val, size_bytes) = read_encoded_value(&data[offset..]) + let (size_val, size_bytes) = read_communal_length(&data[offset..]) .ok_or(Error("Invalid size in NEAR COMDEF"))?; offset += size_bytes; - size_val + (size_val as u64, None) + } + // Borland extension: the data type is a segment index, and a + // single size in bytes follows. This defines a virtual + // segment that contains the symbol's data, which can be + // referenced by other records using a segment index with + // bit 14 set. + 0x00..=0x5F => { + let (size_val, size_bytes) = read_communal_length(&data[offset..]) + .ok_or(Error("Invalid size in COMDEF"))?; + offset += size_bytes; + let base_segment = self.segdef_section(data_type as u16).ok(); + (size_val as u64, Some(base_segment)) } _ => { return Err(Error("Invalid data type in COMDEF/LCOMDEF record")); } }; - let sym_idx = self.symbols.len(); - self.symbols.push(OmfSymbol { - symbol_index: sym_idx, - name, - class, - group_index: 0, - segment_index: 0, - frame_number: 0, - offset: communal_length, // Store size in offset field - type_index, - kind: read::SymbolKind::Data, - }); + let index = SymbolIndex(self.symbols.len()); + if let Some(base_segment) = base_segment { + // Synthesize a section for the Borland virtual segment, which + // behaves like a COMDAT. + let section_index = self.sections.len(); + self.virtual_segdefs.push(section_index); + self.sections.push(OmfSegment { + name, + class: base_segment.map_or(&[][..], |i| self.sections[i].class), + name_index: 0, + class_index: base_segment.map_or(0, |i| self.sections[i].class_index), + overlay_index: 0, + alignment: base_segment + .map_or(omf::SegmentAlignment::Byte, |i| self.sections[i].alignment), + combination: omf::SegmentCombination::Private, + use32: base_segment.is_some_and(|i| self.sections[i].use32), + length: communal_length, + kind: None, + comdat: true, + data_chunks: Vec::new(), + relocations: Vec::new(), + }); - // Add to external_order for symbols that contribute to external-name table - self.external_order.push(read::SymbolIndex(sym_idx)); + self.symbols.push(OmfSymbol { + index, + name, + class, + section: Some(SectionIndex(section_index + 1)), + absolute: false, + frame_number: 0, + offset: 0, + size: communal_length, + type_index, + kind: SymbolKind::Unknown, // Will be computed later + }); + + self.comdats.push(OmfComdatData { + name, + section: section_index, + symbol: index, + selection: OmfComdatSelection::UseAny, + }); + } else { + self.symbols.push(OmfSymbol { + index, + name, + class, + section: None, + absolute: false, + frame_number: 0, + offset: 0, + size: communal_length, + type_index, + kind: SymbolKind::Data, + }); + } + + // Communal symbols contribute to the external-name table + self.external_order.push(index); } Ok(()) } - fn parse_comdat(&mut self, data: &'data [u8], is_32bit: bool) -> Result<()> { + fn parse_comdat(&mut self, data: &'data [u8], is_32bit: bool) -> Result<(usize, u32)> { let mut offset = 0; // Parse flags byte if offset >= data.len() { return Err(Error("Truncated COMDAT record")); } - let _flags = data[offset]; + let flags = data[offset]; offset += 1; + let continuation = (flags & 0x01) != 0; + let iterated = (flags & 0x02) != 0; + let local = (flags & 0x04) != 0; // Parse attributes byte if offset >= data.len() { @@ -599,41 +737,25 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let attributes = data[offset]; offset += 1; - // Extract selection criteria from high nibble of attributes - let selection = match (attributes >> 4) & 0x0F { - 0x00 => OmfComdatSelection::Explicit, // No match - 0x01 => OmfComdatSelection::UseAny, // Pick any - 0x02 => OmfComdatSelection::SameSize, // Same size - 0x03 => OmfComdatSelection::ExactMatch, // Exact match - _ => OmfComdatSelection::UseAny, - }; - - // Extract allocation type from low nibble of attributes + // Low nibble is the allocation type let allocation_type = attributes & 0x0F; - - // Parse align/segment index field - let (segment_index, size) = - read_index(&data[offset..]).ok_or(Error("Invalid COMDAT segment index"))?; - offset += size; - - // Determine alignment - if segment index is 0-7, it's actually an alignment value - let alignment = if segment_index <= 7 { - match segment_index { - 0 => omf::SegmentAlignment::Absolute, // Use value from SEGDEF - 1 => omf::SegmentAlignment::Byte, - 2 => omf::SegmentAlignment::Word, - 3 => omf::SegmentAlignment::Paragraph, - 4 => omf::SegmentAlignment::Page, - 5 => omf::SegmentAlignment::DWord, - 6 => omf::SegmentAlignment::Page4K, - _ => omf::SegmentAlignment::Byte, - } - } else { - omf::SegmentAlignment::Byte // Default alignment + // High nibble is the selection criteria + let selection = match (attributes >> 4) & 0x0F { + 0x00 => OmfComdatSelection::Explicit, // No match allowed + 0x01 => OmfComdatSelection::UseAny, // Pick any + 0x02 => OmfComdatSelection::SameSize, // Same size + _ => OmfComdatSelection::ExactMatch, // Exact match }; - // Parse data offset - let _data_offset = if is_32bit { + // Parse align byte + if offset >= data.len() { + return Err(Error("Truncated COMDAT record")); + } + let align = data[offset]; + offset += 1; + + // Parse enumerated data offset + let data_offset = if is_32bit { if offset + 4 > data.len() { return Err(Error("Truncated COMDAT record")); } @@ -659,19 +781,23 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { read_index(&data[offset..]).ok_or(Error("Invalid type index in COMDAT record"))?; offset += size; - // Parse public base (only if allocation type is 0x00 - Explicit) + // Parse the public base, which is present only for explicit allocation + let mut base_segment = None; if allocation_type == 0x00 { - // Has public base (Base Group, Base Segment, Base Frame) let (_group_index, size) = read_index(&data[offset..]).ok_or(Error("Invalid group index in COMDAT record"))?; offset += size; - let (_seg_idx, size) = read_index(&data[offset..]) + let (segment_index, size) = read_index(&data[offset..]) .ok_or(Error("Invalid segment index in COMDAT record"))?; offset += size; - if _seg_idx == 0 { - if offset + 2 <= data.len() { - offset += 2; // Skip frame number + if segment_index == 0 { + // Skip frame number + if offset + 2 > data.len() { + return Err(Error("Truncated COMDAT record")); } + offset += 2; + } else { + base_segment = Some(self.segdef_section(segment_index)?); } } @@ -679,36 +805,106 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let (name_index, size) = read_index(&data[offset..]).ok_or(Error("Invalid name index in COMDAT record"))?; offset += size; - - // Look up the name from the names table - let name = name_index - .checked_sub(1) - .and_then(|i| self.names.get(i as usize).copied()) - .unwrap_or(b""); + let name = self + .get_name(name_index) + .ok_or(Error("Invalid name index in COMDAT record"))?; // Remaining data is the COMDAT content - let comdat_data = &data[offset..]; + let chunk = if iterated { + OmfDataChunk::Iterated { + data: &data[offset..], + is_32bit, + } + } else { + OmfDataChunk::Direct(&data[offset..]) + }; + let chunk_end = data_offset as u64 + chunk.expanded_len()?; + + // Continuation records (and repeated COMDAT records for the same name) + // add data to the previously defined COMDAT. + if continuation { + let comdat = self + .comdats + .iter() + .rev() + .find(|comdat| comdat.name == name) + .ok_or(Error("COMDAT continuation without a previous COMDAT"))?; + let section_index = comdat.section; + let section = &mut self.sections[section_index]; + section.data_chunks.push((data_offset, chunk)); + if chunk_end > section.length { + section.length = chunk_end; + } + return Ok((section_index, data_offset)); + } + + // Determine the section kind, alignment, and use32 from the + // allocation type, falling back to the base segment for explicit + // allocation. + let (kind, use32) = match allocation_type { + 0x00 => (None, base_segment.is_some_and(|i| self.sections[i].use32)), + 0x01 => (Some(SectionKind::Text), false), // Far code + 0x02 => (Some(SectionKind::Data), false), // Far data + 0x03 => (Some(SectionKind::Text), true), // Code32 + 0x04 => (Some(SectionKind::Data), true), // Data32 + _ => (None, false), + }; + let alignment = match align { + 0 => base_segment.map_or(omf::SegmentAlignment::Byte, |i| self.sections[i].alignment), + 1 => omf::SegmentAlignment::Byte, + 2 => omf::SegmentAlignment::Word, + 3 => omf::SegmentAlignment::Paragraph, + 4 => omf::SegmentAlignment::Page, + 5 => omf::SegmentAlignment::DWord, + 6 => omf::SegmentAlignment::Page4K, + _ => omf::SegmentAlignment::Byte, + }; + + // Synthesize a section for the COMDAT. + let section_index = self.sections.len(); + self.sections.push(OmfSegment { + name, + class: base_segment.map_or(&[][..], |i| self.sections[i].class), + name_index, + class_index: base_segment.map_or(0, |i| self.sections[i].class_index), + overlay_index: 0, + alignment, + combination: omf::SegmentCombination::Private, + use32, + length: chunk_end, + kind, + comdat: true, + data_chunks: alloc::vec![(data_offset, chunk)], + relocations: Vec::new(), + }); + + // Synthesize a symbol for the COMDAT. + let symbol_index = SymbolIndex(self.symbols.len()); + self.symbols.push(OmfSymbol { + index: symbol_index, + name, + class: if local { + OmfSymbolClass::LocalComdat + } else { + OmfSymbolClass::Comdat + }, + section: Some(SectionIndex(section_index + 1)), + absolute: false, + frame_number: 0, + offset: 0, + size: 0, // Will be computed later + type_index: 0, + kind: SymbolKind::Unknown, // Will be computed later + }); self.comdats.push(OmfComdatData { name, - segment_index, + section: section_index, + symbol: symbol_index, selection, - alignment, - data: comdat_data, }); - Ok(()) - } - - fn parse_comment(&mut self, data: &'data [u8]) -> Result<()> { - if data.len() < 2 { - return Ok(()); // Ignore truncated comments - } - - let _comment_type = data[0]; // Usually 0x00 for non-purge, 0x40 for purge - let _comment_class = data[1]; - - Ok(()) + Ok((section_index, data_offset)) } fn parse_ledata(&mut self, data: &'data [u8], is_32bit: bool) -> Result<(usize, u32)> { @@ -718,10 +914,7 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let (segment_index, size) = read_index(data).ok_or(Error("Invalid segment index in LEDATA"))?; offset += size; - - if segment_index == 0 || segment_index > self.segments.len() as u16 { - return Err(Error("Invalid segment index in LEDATA")); - } + let section_index = self.segdef_section(segment_index)?; // Parse data offset let data_offset = if is_32bit { @@ -745,26 +938,68 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { off }; - // Store reference to data chunk - let seg_idx = (segment_index - 1) as usize; - let segment = &mut self.segments[seg_idx]; - // Store the data chunk reference if offset < data.len() { - segment + self.sections[section_index] .data_chunks .push((data_offset, OmfDataChunk::Direct(&data[offset..]))); } - Ok((seg_idx, data_offset)) + Ok((section_index, data_offset)) + } + + fn parse_lidata(&mut self, data: &'data [u8], is_32bit: bool) -> Result<(usize, u32)> { + let mut offset = 0; + + // Read segment index + let (segment_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid segment index in LIDATA"))?; + offset += size; + let section_index = self.segdef_section(segment_index)?; + + // Read data offset + let data_offset = if is_32bit { + if offset + 4 > data.len() { + return Err(Error("Truncated LIDATA record")); + } + let off = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + offset += 4; + off + } else { + if offset + 2 > data.len() { + return Err(Error("Truncated LIDATA record")); + } + let off = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; + offset += 2; + off + }; + + // Store the unexpanded data; it is expanded on demand. + if offset < data.len() { + let chunk = OmfDataChunk::Iterated { + data: &data[offset..], + is_32bit, + }; + // Validate the iterated data blocks. + chunk.expanded_len()?; + self.sections[section_index] + .data_chunks + .push((data_offset, chunk)); + } + + Ok((section_index, data_offset)) } fn parse_fixupp( &mut self, data: &'data [u8], is_32bit: bool, - seg_idx: usize, - data_offset: u32, + last_data: Option<(usize, u32)>, frame_threads: &mut [Option; 4], target_threads: &mut [Option; 4], ) -> Result<()> { @@ -807,12 +1042,19 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { } } else { // FIXUP subrecord + let (section_index, data_offset) = last_data.ok_or(Error( + "FIXUP subrecord without preceding LEDATA/LIDATA/COMDAT", + ))?; + if offset + 1 > data.len() { return Err(Error("Truncated FIXUP location")); } let locat = data[offset] as u32 | (((b as u32) & 0x03) << 8); offset += 1; + // The M bit determines segment-relative vs self-relative. + let is_segment_relative = (b & 0x40) != 0; + let location = match (b >> 2) & 0x0F { 0 => omf::FixupLocation::LowByte, 1 => omf::FixupLocation::Offset, @@ -823,7 +1065,7 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { 9 => omf::FixupLocation::Offset32, 11 => omf::FixupLocation::Pointer48, 13 => omf::FixupLocation::LoaderOffset32, - _ => continue, // Skip unknown fixup types + _ => return Err(Error("Invalid FIXUP location type")), }; // Parse fix data byte @@ -840,31 +1082,16 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let thread_num = ((fix_data >> 4) & 0x03) as usize; match frame_threads[thread_num] { Some(thread) => { - let method = match thread.method { - 0 => FrameMethod::SegmentIndex, - 1 => FrameMethod::GroupIndex, - 2 => FrameMethod::ExternalIndex, - 3 => FrameMethod::FrameNumber, - 4 => FrameMethod::Location, - 5 => FrameMethod::Target, - _ => return Err(Error("Invalid frame method in thread")), - }; + let method = FrameMethod::parse(thread.method) + .ok_or(Error("Invalid frame method in thread"))?; (method, thread.index) } None => return Err(Error("Undefined frame thread in FIXUP")), } } else { // F=0: Read frame datum - let method_bits = (fix_data >> 4) & 0x07; - let method = match method_bits { - 0 => FrameMethod::SegmentIndex, - 1 => FrameMethod::GroupIndex, - 2 => FrameMethod::ExternalIndex, - 3 => FrameMethod::FrameNumber, - 4 => FrameMethod::Location, - 5 => FrameMethod::Target, - _ => return Err(Error("Invalid frame method in FIXUP")), - }; + let method = FrameMethod::parse((fix_data >> 4) & 0x07) + .ok_or(Error("Invalid frame method in FIXUP"))?; let index = match method { FrameMethod::SegmentIndex | FrameMethod::GroupIndex @@ -896,28 +1123,17 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let thread_num = (fix_data & 0x03) as usize; match target_threads[thread_num] { Some(thread) => { - // Only check the low 2 bits of method for target - let method = match thread.method & 0x03 { - 0 => TargetMethod::SegmentIndex, - 1 => TargetMethod::GroupIndex, - 2 => TargetMethod::ExternalIndex, - 3 => TargetMethod::FrameNumber, - _ => return Err(Error("Invalid target method in thread")), - }; + // Only the low 2 bits of the thread method apply to targets + let method = TargetMethod::parse(thread.method & 0x03) + .ok_or(Error("Invalid target method in thread"))?; (method, thread.index) } None => return Err(Error("Undefined target thread in FIXUP")), } } else { // T=0: Read target datum - // Only check the low 2 bits of method for target - let method = match fix_data & 0x03 { - 0 => TargetMethod::SegmentIndex, - 1 => TargetMethod::GroupIndex, - 2 => TargetMethod::ExternalIndex, - 3 => TargetMethod::FrameNumber, - _ => return Err(Error("Invalid frame method in FIXUP")), - }; + let method = TargetMethod::parse(fix_data & 0x03) + .ok_or(Error("Invalid target method in FIXUP"))?; let index = match method { TargetMethod::SegmentIndex | TargetMethod::GroupIndex @@ -945,32 +1161,30 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { let has_displacement = (fix_data & 0x04) == 0; let target_displacement = if has_displacement { if is_32bit { - if offset + 4 <= data.len() { - let disp = u32::from_le_bytes([ - data[offset], - data[offset + 1], - data[offset + 2], - data[offset + 3], - ]); - offset += 4; - disp - } else { + if offset + 4 > data.len() { return Err(Error("Truncated FIXUP 32-bit displacement")); } - } else if offset + 2 <= data.len() { + let disp = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + offset += 4; + disp + } else { + if offset + 2 > data.len() { + return Err(Error("Truncated FIXUP 16-bit displacement")); + } let disp = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; offset += 2; disp - } else { - return Err(Error("Truncated FIXUP 16-bit displacement")); } } else { 0 }; - // Extract M-bit (bit 6 of fix_data) - let is_segment_relative = (fix_data & 0x40) != 0; - self.segments[seg_idx].relocations.push(OmfFixup { + self.sections[section_index].relocations.push(OmfFixup { offset: data_offset + locat, location, frame_method, @@ -986,63 +1200,18 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { Ok(()) } - fn parse_lidata(&mut self, data: &'data [u8], is_32bit: bool) -> Result<(usize, u32)> { - let mut offset = 0; - - // Read segment index - let (segment_index, size) = - read_index(&data[offset..]).ok_or(Error("Invalid segment index in LIDATA"))?; - offset += size; - - if segment_index == 0 || segment_index > self.segments.len() as u16 { - return Err(Error("Invalid segment index in LIDATA")); - } - - // Read data offset - let data_offset = if is_32bit { - if offset + 4 > data.len() { - return Err(Error("Truncated LIDATA record")); - } - let off = u32::from_le_bytes([ - data[offset], - data[offset + 1], - data[offset + 2], - data[offset + 3], - ]); - offset += 4; - off - } else { - if offset + 2 > data.len() { - return Err(Error("Truncated LIDATA record")); - } - let off = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; - offset += 2; - off - }; - - // For LIDATA, we need to store the unexpanded data and expand on demand - let seg_idx = (segment_index - 1) as usize; - if offset < data.len() { - self.segments[seg_idx] - .data_chunks - .push((data_offset, OmfDataChunk::Iterated(&data[offset..]))); - } - - Ok((seg_idx, data_offset)) - } - - /// Get the module name + /// Get the module name from the THEADR/LHEADR record. pub fn module_name(&self) -> Option<&'data str> { self.module_name } - /// Get the segments as a slice - pub fn raw_segments(&self) -> &[OmfSegment<'data>] { - &self.segments + /// Get the parsed sections, which include both segments and COMDATs. + pub fn raw_sections(&self) -> &[OmfSegment<'data>] { + &self.sections } /// Get symbol by external-name index (1-based, as used in FIXUPP records) - pub fn external_symbol(&self, external_index: u16) -> Option<&OmfSymbol<'data>> { + pub(super) fn external_symbol(&self, external_index: u16) -> Option<&OmfSymbol<'data>> { let symbol_index = self .external_order .get(external_index.checked_sub(1)? as usize)?; @@ -1055,6 +1224,12 @@ impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { self.names.get(name_index as usize).copied() } + /// Get a group's name by group index (1-based) + pub(super) fn group_name(&self, index: u16) -> Option<&'data [u8]> { + let group = self.groups.get(index.checked_sub(1)? as usize)?; + self.get_name(group.name_index) + } + /// Get all symbols (for iteration) pub fn raw_symbols(&self) -> &[OmfSymbol<'data>] { &self.symbols @@ -1153,7 +1328,7 @@ impl<'data, R: ReadRef<'data>> Object<'data> for OmfFile<'data, R> { .0 .checked_sub(1) .ok_or(Error("Invalid section index"))?; - if idx < self.segments.len() { + if idx < self.sections.len() { Ok(OmfSection { file: self, index: idx, @@ -1178,11 +1353,10 @@ impl<'data, R: ReadRef<'data>> Object<'data> for OmfFile<'data, R> { } fn symbol_by_index(&self, index: SymbolIndex) -> Result> { - let idx = index.0; - if idx >= self.symbols.len() { - return Err(Error("Symbol index out of bounds")); - } - Ok(self.symbols[idx].clone()) + self.symbols + .get(index.0) + .cloned() + .ok_or(Error("Symbol index out of bounds")) } fn symbols(&self) -> Self::SymbolIterator<'_> { @@ -1199,7 +1373,7 @@ impl<'data, R: ReadRef<'data>> Object<'data> for OmfFile<'data, R> { fn dynamic_symbols(&self) -> Self::SymbolIterator<'_> { OmfSymbolIterator { file: self, - index: usize::MAX, // Empty iterator + index: self.symbols.len(), // Empty iterator } } @@ -1212,36 +1386,18 @@ impl<'data, R: ReadRef<'data>> Object<'data> for OmfFile<'data, R> { } fn imports(&self) -> Result>> { - Ok(self - .raw_symbols() - .iter() - .filter(|sym| { - matches!( - sym.class, - OmfSymbolClass::External | OmfSymbolClass::ComdatExternal - ) - }) - .map(|ext| Import { - library: ByteString(b""), - name: ByteString(ext.name), - }) - .collect()) + // TODO: this could return undefined symbols, but not needed yet. + Ok(Vec::new()) } fn exports(&self) -> Result>> { - Ok(self - .raw_symbols() - .iter() - .filter(|sym| sym.class == OmfSymbolClass::Public) - .map(|pub_sym| Export { - name: ByteString(pub_sym.name), - address: pub_sym.offset as u64, - }) - .collect()) + // TODO: this could return global symbols, but not needed yet. + Ok(Vec::new()) } fn has_debug_symbols(&self) -> bool { - false + self.sections() + .any(|section| section.kind() == SectionKind::Debug) } fn mach_uuid(&self) -> Result> { @@ -1288,89 +1444,141 @@ struct ThreadDef { /// Target method types for fixups #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] pub(super) enum TargetMethod { /// Segment index - SegmentIndex = 0, + SegmentIndex, /// Group index - GroupIndex = 1, + GroupIndex, /// External index - ExternalIndex = 2, + ExternalIndex, /// Frame number (absolute) - FrameNumber = 3, + FrameNumber, +} + +impl TargetMethod { + fn parse(method: u8) -> Option { + Some(match method { + 0 => TargetMethod::SegmentIndex, + 1 => TargetMethod::GroupIndex, + 2 => TargetMethod::ExternalIndex, + 3 => TargetMethod::FrameNumber, + _ => return None, + }) + } } /// Frame method types for fixups #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] pub(super) enum FrameMethod { /// Segment index - SegmentIndex = 0, + SegmentIndex, /// Group index - GroupIndex = 1, + GroupIndex, /// External index - ExternalIndex = 2, + ExternalIndex, /// Frame number (absolute) - FrameNumber = 3, - /// Location (use fixup location) - Location = 4, - /// Target (use target's frame) - Target = 5, + FrameNumber, + /// Location (use the frame containing the fixup location) + Location, + /// Target (use the target's frame) + Target, } -/// Expand a LIDATA block into a newly allocated buffer -pub(super) fn expand_lidata_block(data: &[u8]) -> Result> { - let (orig_size, expanded_size) = lidata_block_expanded_size(data)?; - let mut result = vec![0u8; expanded_size]; - let mut write_offset = 0usize; - let consumed = expand_lidata_block_into(data, &mut result, &mut write_offset)?; +impl FrameMethod { + fn parse(method: u8) -> Option { + Some(match method { + 0 => FrameMethod::SegmentIndex, + 1 => FrameMethod::GroupIndex, + 2 => FrameMethod::ExternalIndex, + 3 => FrameMethod::FrameNumber, + 4 => FrameMethod::Location, + 5 => FrameMethod::Target, + _ => return None, + }) + } +} +/// Expand iterated data blocks (from LIDATA or COMDAT records) into a newly +/// allocated buffer. +/// +/// The data may contain multiple consecutive iterated data blocks. +pub(super) fn expand_iterated_data(data: &[u8], is_32bit: bool) -> Result> { + let expanded_size = usize::try_from(iterated_data_expanded_len(data, is_32bit)?) + .map_err(|_| Error("LIDATA expanded size overflow"))?; + let mut result = alloc::vec![0u8; expanded_size]; + let mut offset = 0; + let mut write_offset = 0; + while offset < data.len() { + offset += expand_iterated_block(&data[offset..], is_32bit, &mut result, &mut write_offset)?; + } debug_assert_eq!(write_offset, expanded_size); - debug_assert_eq!(consumed, orig_size); - Ok(result) } -fn expand_lidata_block_into( +/// Return the total expanded size of consecutive iterated data blocks. +pub(super) fn iterated_data_expanded_len(data: &[u8], is_32bit: bool) -> Result { + let mut offset = 0; + let mut expanded = 0u64; + while offset < data.len() { + let (consumed, block_expanded) = iterated_block_expanded_len(&data[offset..], is_32bit)?; + offset += consumed; + expanded = expanded + .checked_add(block_expanded) + .ok_or(Error("LIDATA expanded size overflow"))?; + } + Ok(expanded) +} + +/// Read the repeat count and block count of an iterated data block. +/// +/// Returns the counts and the number of bytes consumed. +fn read_iterated_block_header(data: &[u8], is_32bit: bool) -> Result<(u32, u16, usize)> { + let mut offset = 0; + let repeat_count = if is_32bit { + if data.len() < 4 { + return Err(Error("Truncated LIDATA block")); + } + offset += 4; + u32::from_le_bytes([data[0], data[1], data[2], data[3]]) + } else { + if data.len() < 2 { + return Err(Error("Truncated LIDATA block")); + } + offset += 2; + u16::from_le_bytes([data[0], data[1]]) as u32 + }; + if data.len() < offset + 2 { + return Err(Error("Truncated LIDATA block")); + } + let block_count = u16::from_le_bytes([data[offset], data[offset + 1]]); + offset += 2; + Ok((repeat_count, block_count, offset)) +} + +/// Expand a single iterated data block, returning the number of bytes consumed. +fn expand_iterated_block( data: &[u8], + is_32bit: bool, output: &mut [u8], write_offset: &mut usize, ) -> Result { - let mut offset = 0; - - let (repeat_count, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid repeat count in LIDATA block"))?; - offset += size; - - if repeat_count == 0 { - return lidata_block_size(data); - } - - let repeat_count = repeat_count as usize; - - let (block_count, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid block count in LIDATA block"))?; - offset += size; + let (repeat_count, block_count, mut offset) = read_iterated_block_header(data, is_32bit)?; if block_count == 0 { + // Leaf block: 1-byte length followed by data bytes. if offset >= data.len() { - return Ok(offset); + return Err(Error("Truncated LIDATA block")); } - let data_length = data[offset] as usize; offset += 1; - if offset + data_length > data.len() { return Err(Error("Truncated LIDATA block")); } - let block_data = &data[offset..offset + data_length]; offset += data_length; for _ in 0..repeat_count { - let end = write_offset - .checked_add(data_length) - .ok_or(Error("LIDATA expanded size overflow"))?; + let end = *write_offset + data_length; if end > output.len() { return Err(Error("LIDATA expanded size mismatch")); } @@ -1378,136 +1586,65 @@ fn expand_lidata_block_into( *write_offset = end; } } else { - let mut block_offset = offset; + // Nested blocks: expand one iteration, then repeat it. let iteration_start = *write_offset; - for _ in 0..block_count { - let block_size = lidata_block_size(&data[block_offset..])?; - let block_consumed = - expand_lidata_block_into(&data[block_offset..], output, write_offset)?; - - debug_assert_eq!(block_size, block_consumed); - block_offset = block_offset - .checked_add(block_size) - .ok_or(Error("LIDATA block size overflow"))?; - if block_offset > data.len() { + if offset >= data.len() { return Err(Error("Truncated LIDATA block")); } + offset += expand_iterated_block(&data[offset..], is_32bit, output, write_offset)?; } - let iteration_len = *write_offset - iteration_start; for _ in 1..repeat_count { let dest_start = *write_offset; - let dest_end = dest_start - .checked_add(iteration_len) - .ok_or(Error("LIDATA expanded size overflow"))?; + let dest_end = dest_start + iteration_len; if dest_end > output.len() { return Err(Error("LIDATA expanded size mismatch")); } - if iteration_len != 0 { - output.copy_within(iteration_start..iteration_start + iteration_len, dest_start); - } + output.copy_within(iteration_start..iteration_start + iteration_len, dest_start); *write_offset = dest_end; } - - offset = block_offset; } Ok(offset) } -fn lidata_block_expanded_size(data: &[u8]) -> Result<(usize, usize)> { - let mut offset = 0; +/// Return the consumed and expanded size of a single iterated data block. +fn iterated_block_expanded_len(data: &[u8], is_32bit: bool) -> Result<(usize, u64)> { + let (repeat_count, block_count, mut offset) = read_iterated_block_header(data, is_32bit)?; - let (repeat_count, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid repeat count in LIDATA block"))?; - offset += size; - - if repeat_count == 0 { - let consumed = lidata_block_size(data)?; - if consumed > data.len() { + let single_iteration = if block_count == 0 { + // Leaf block: 1-byte length followed by data bytes. + if offset >= data.len() { return Err(Error("Truncated LIDATA block")); } - return Ok((consumed, 0)); - } - - let (block_count, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid block count in LIDATA block"))?; - offset += size; - - if block_count == 0 { - if offset >= data.len() { - return Ok((offset, 0)); - } - let data_length = data[offset] as usize; offset += 1; - if offset + data_length > data.len() { return Err(Error("Truncated LIDATA block")); } - offset += data_length; - - let expanded = data_length - .checked_mul(repeat_count as usize) - .ok_or(Error("LIDATA expanded size overflow"))?; - Ok((offset, expanded)) + data_length as u64 } else { - let mut block_offset = offset; - let mut single_iteration = 0usize; - + let mut single_iteration = 0u64; for _ in 0..block_count { - let (consumed, expanded) = lidata_block_expanded_size(&data[block_offset..])?; - block_offset = block_offset - .checked_add(consumed) - .ok_or(Error("LIDATA block size overflow"))?; - if block_offset > data.len() { + if offset >= data.len() { return Err(Error("Truncated LIDATA block")); } + let (consumed, expanded) = iterated_block_expanded_len(&data[offset..], is_32bit)?; + offset += consumed; single_iteration = single_iteration .checked_add(expanded) .ok_or(Error("LIDATA expanded size overflow"))?; } + single_iteration + }; - let expanded = single_iteration - .checked_mul(repeat_count as usize) - .ok_or(Error("LIDATA expanded size overflow"))?; - - Ok((block_offset, expanded)) - } -} - -/// Helper function to calculate LIDATA block size -fn lidata_block_size(data: &[u8]) -> Result { - let mut offset = 0; - - // Read repeat count - let (_, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid repeat count in LIDATA block"))?; - offset += size; - - // Read block count - let (block_count, size) = - read_encoded_value(&data[offset..]).ok_or(Error("Invalid block count in LIDATA block"))?; - offset += size; - - if block_count == 0 { - // Leaf block - if offset >= data.len() { - return Ok(offset); - } - let data_length = data[offset] as usize; - offset += 1 + data_length; - } else { - // Nested blocks - for _ in 0..block_count { - offset += lidata_block_size(&data[offset..])?; - } - } - - Ok(offset) + let expanded = single_iteration + .checked_mul(repeat_count as u64) + .ok_or(Error("LIDATA expanded size overflow"))?; + Ok((offset, expanded)) } /// Helper to read an OMF index (1 or 2 bytes) @@ -1544,43 +1681,32 @@ fn read_counted_string(data: &[u8]) -> Option<(&[u8], usize)> { } } -/// Read an encoded value (used in LIDATA for repeat counts and block counts) -/// Returns the value and number of bytes consumed -fn read_encoded_value(data: &[u8]) -> Option<(u32, usize)> { - if data.is_empty() { - return None; - } - - let first_byte = data[0]; - if first_byte < 0x80 { +/// Read a COMDEF communal length. +/// +/// Returns the value and number of bytes consumed. +fn read_communal_length(data: &[u8]) -> Option<(u32, usize)> { + let first_byte = *data.first()?; + match first_byte { // Single byte value (0-127) - Some((first_byte as u32, 1)) - } else if first_byte == 0x81 { - // Two byte value: 0x81 followed by 16-bit little-endian value - if data.len() >= 3 { - let value = u16::from_le_bytes([data[1], data[2]]) as u32; - Some((value, 3)) - } else { - None + 0..=0x80 => Some((first_byte as u32, 1)), + // 0x81 followed by a 16-bit little-endian value + 0x81 => { + let bytes = data.get(1..3)?; + Some((u16::from_le_bytes([bytes[0], bytes[1]]) as u32, 3)) } - } else if first_byte == 0x84 { - // Three byte value: 0x84 followed by 24-bit little-endian value - if data.len() >= 4 { - let value = u32::from_le_bytes([data[1], data[2], data[3], 0]); - Some((value, 4)) - } else { - None + // 0x84 followed by a 24-bit little-endian value + 0x84 => { + let bytes = data.get(1..4)?; + Some((u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]), 4)) } - } else if first_byte == 0x88 { - // Four byte value: 0x88 followed by 32-bit little-endian value - if data.len() >= 5 { - let value = u32::from_le_bytes([data[1], data[2], data[3], data[4]]); - Some((value, 5)) - } else { - None + // 0x88 followed by a 32-bit little-endian value + 0x88 => { + let bytes = data.get(1..5)?; + Some(( + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + 5, + )) } - } else { - // Unknown encoding - None + _ => None, } } diff --git a/src/read/omf/relocation.rs b/src/read/omf/relocation.rs index aa77538..c5e4864 100644 --- a/src/read/omf/relocation.rs +++ b/src/read/omf/relocation.rs @@ -1,7 +1,7 @@ use crate::read::ReadRef; use crate::{ omf, Relocation, RelocationEncoding, RelocationFlags, RelocationKind, RelocationTarget, - SectionIndex, SymbolIndex, + SectionIndex, }; use super::{FrameMethod, OmfFile, TargetMethod}; @@ -9,7 +9,7 @@ use super::{FrameMethod, OmfFile, TargetMethod}; /// An OMF fixup (relocation entry). #[derive(Debug, Clone)] pub(super) struct OmfFixup { - /// Offset in segment where fixup is applied + /// Offset in the section where the fixup is applied pub(super) offset: u32, /// Location type (what to patch) pub(super) location: omf::FixupLocation, @@ -23,97 +23,122 @@ pub(super) struct OmfFixup { pub(super) target_index: u16, /// Target displacement pub(super) target_displacement: u32, - /// M-bit: true for segment-relative, false for PC-relative + /// M-bit: true for segment-relative, false for self-relative pub(super) is_segment_relative: bool, } -/// An iterator over OMF relocations. +impl omf::FixupLocation { + /// The size in bytes of the location being fixed up. + fn byte_size(self) -> u8 { + match self { + omf::FixupLocation::LowByte | omf::FixupLocation::HighByte => 1, + omf::FixupLocation::Offset + | omf::FixupLocation::LoaderOffset + | omf::FixupLocation::Base => 2, + omf::FixupLocation::Pointer + | omf::FixupLocation::Offset32 + | omf::FixupLocation::LoaderOffset32 => 4, + omf::FixupLocation::Pointer48 => 6, + } + } +} + +/// An iterator for the relocations in an [`OmfSection`](super::OmfSection). #[derive(Debug)] pub struct OmfRelocationIterator<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>, - pub(super) segment_index: usize, + pub(super) section_index: usize, pub(super) index: usize, } +impl<'data, 'file, R: ReadRef<'data>> OmfRelocationIterator<'data, 'file, R> { + /// Resolve a 1-based segment index to a section index. + fn target_section(&self, segment_index: u16) -> Option { + let section = self.file.segdef_section(segment_index).ok()?; + Some(SectionIndex(section + 1)) + } + + /// Return true if the frame of the fixup is the FLAT group, in which case + /// segment-relative fixups resolve to linear addresses. + fn frame_is_flat(&self, reloc: &OmfFixup) -> bool { + reloc.frame_method == FrameMethod::GroupIndex + && self.file.group_name(reloc.frame_index) == Some(b"FLAT") + } +} + impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfRelocationIterator<'data, 'file, R> { type Item = (u64, Relocation); fn next(&mut self) -> Option { - let relocations = &self.file.segments[self.segment_index].relocations; + let relocations = &self.file.sections[self.section_index].relocations; let reloc = relocations.get(self.index)?; self.index += 1; - let (mut kind, size, base_addend) = match reloc.location { - omf::FixupLocation::LowByte => (RelocationKind::Absolute, 8, 0), - omf::FixupLocation::HighByte => (RelocationKind::Absolute, 8, 0), - omf::FixupLocation::Offset | omf::FixupLocation::LoaderOffset => { - if reloc.is_segment_relative { - (RelocationKind::SectionOffset, 16, 0) - } else { - (RelocationKind::Relative, 16, -2) - } - } - omf::FixupLocation::Offset32 | omf::FixupLocation::LoaderOffset32 => { - if reloc.is_segment_relative { - (RelocationKind::SectionOffset, 32, 0) - } else { - (RelocationKind::Relative, 32, -4) - } - } - omf::FixupLocation::Base => { - if matches!(reloc.target_method, TargetMethod::SegmentIndex) { - (RelocationKind::SectionIndex, 16, 0) - } else { - (RelocationKind::Unknown, 16, 0) - } - } - omf::FixupLocation::Pointer => (RelocationKind::Absolute, 32, 0), - omf::FixupLocation::Pointer48 => (RelocationKind::Absolute, 48, 0), - }; - - if matches!(kind, RelocationKind::SectionOffset) - && !matches!(reloc.target_method, TargetMethod::SegmentIndex) - { - kind = RelocationKind::Unknown; - } - - if matches!( - reloc.location, - omf::FixupLocation::LoaderOffset | omf::FixupLocation::LoaderOffset32 - ) && matches!(reloc.frame_method, FrameMethod::ExternalIndex) - { - kind = RelocationKind::Unknown; - } - - if matches!(reloc.target_method, TargetMethod::GroupIndex) { - kind = RelocationKind::Unknown; - } + let size = reloc.location.byte_size() * 8; let target = match reloc.target_method { - TargetMethod::SegmentIndex => { - if let Some(zero_based) = reloc.target_index.checked_sub(1) { - let index = zero_based as usize; - if index < self.file.segments.len() { - RelocationTarget::Section(SectionIndex(index)) - } else { - RelocationTarget::Absolute - } - } else { - RelocationTarget::Absolute - } - } + TargetMethod::SegmentIndex => match self.target_section(reloc.target_index) { + Some(section) => RelocationTarget::Section(section), + None => RelocationTarget::Absolute, + }, TargetMethod::ExternalIndex => { - // External indices in OMF are 1-based indices into the external-name table - if let Some(symbol) = self.file.external_symbol(reloc.target_index) { - RelocationTarget::Symbol(SymbolIndex(symbol.symbol_index)) - } else { - RelocationTarget::Absolute + // External indices are 1-based indices into the external-name table + match self.file.external_symbol(reloc.target_index) { + Some(symbol) => RelocationTarget::Symbol(symbol.index), + None => RelocationTarget::Absolute, } } TargetMethod::GroupIndex | TargetMethod::FrameNumber => RelocationTarget::Absolute, }; - let fixup_frame = match reloc.frame_method { + let mut addend = reloc.target_displacement as i64; + let kind = if !reloc.is_segment_relative { + // Self-relative fixups are relative to the end of the location. + addend -= reloc.location.byte_size() as i64; + RelocationKind::Relative + } else { + match reloc.location { + // The segment portion of the target's address. + omf::FixupLocation::Base => { + if reloc.target_method == TargetMethod::SegmentIndex { + RelocationKind::SectionIndex + } else { + RelocationKind::Unknown + } + } + // Far pointers hold a complete segment:offset address. + omf::FixupLocation::Pointer | omf::FixupLocation::Pointer48 => { + RelocationKind::Absolute + } + // The offset portion of the target's address, relative to the + // frame. + omf::FixupLocation::LowByte + | omf::FixupLocation::Offset + | omf::FixupLocation::LoaderOffset + | omf::FixupLocation::Offset32 + | omf::FixupLocation::LoaderOffset32 => { + if self.frame_is_flat(reloc) { + // The FLAT group has a base address of 0, so the + // offset is a linear address. + RelocationKind::Absolute + } else if reloc.frame_method == FrameMethod::Target + || (reloc.frame_method == FrameMethod::SegmentIndex + && reloc.target_method == TargetMethod::SegmentIndex + && reloc.frame_index == reloc.target_index) + { + // The frame is the section containing the target, so + // the offset is a section offset. + RelocationKind::SectionOffset + } else { + RelocationKind::Unknown + } + } + // The high byte of an offset can't be expressed generically. + omf::FixupLocation::HighByte => RelocationKind::Unknown, + } + }; + + let frame = match reloc.frame_method { FrameMethod::SegmentIndex => omf::FixupFrame::Segment(reloc.frame_index), FrameMethod::GroupIndex => omf::FixupFrame::Group(reloc.frame_index), FrameMethod::ExternalIndex => omf::FixupFrame::External(reloc.frame_index), @@ -134,7 +159,7 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfRelocationIterator<'data, encoding: RelocationEncoding::Generic, size, target, - addend: (reloc.target_displacement as i64) + base_addend, + addend, implicit_addend: false, subtractor: None, flags: RelocationFlags::Omf { @@ -144,9 +169,8 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfRelocationIterator<'data, } else { omf::FixupMode::SelfRelative }, - frame: fixup_frame, + frame, target: fixup_target, - // target_displacement: reloc.target_displacement, }, }; diff --git a/src/read/omf/section.rs b/src/read/omf/section.rs index 45dbd24..f5519ac 100644 --- a/src/read/omf/section.rs +++ b/src/read/omf/section.rs @@ -1,5 +1,5 @@ use alloc::borrow::Cow; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use core::str; use crate::read::{ @@ -7,9 +7,12 @@ use crate::read::{ Result, SectionFlags, SectionIndex, SectionKind, }; -use super::{expand_lidata_block, OmfDataChunk, OmfFile, OmfRelocationIterator, OmfSegment}; +use super::{OmfDataChunk, OmfFile, OmfRelocationIterator, OmfSegment}; -/// A section in an OMF file. +/// A section in an [`OmfFile`]. +/// +/// This is either a segment from a SEGDEF record, or a section synthesized +/// from COMDAT records. #[derive(Debug)] pub struct OmfSection<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>, @@ -18,17 +21,17 @@ pub struct OmfSection<'data, 'file, R: ReadRef<'data>> { /// An OMF group definition #[derive(Debug, Clone)] -#[allow(unused)] pub(super) struct OmfGroup { /// Group name index (into names table) pub(super) name_index: u16, - /// Segment indices in this group + /// Segment indices in this group (1-based SEGDEF indices) + #[allow(unused)] pub(super) segments: Vec, } impl<'data, 'file, R: ReadRef<'data>> OmfSection<'data, 'file, R> { - fn segment(&self) -> &OmfSegment<'data> { - &self.file.segments[self.index] + fn segment(&self) -> &'file OmfSegment<'data> { + &self.file.sections[self.index] } } @@ -46,42 +49,20 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, } fn size(&self) -> u64 { - self.segment().length as u64 + self.segment().length } fn align(&self) -> u64 { - match self.segment().alignment { - crate::omf::SegmentAlignment::Byte => 1, - crate::omf::SegmentAlignment::Word => 2, - crate::omf::SegmentAlignment::Paragraph => 16, - crate::omf::SegmentAlignment::Page => 256, - crate::omf::SegmentAlignment::DWord => 4, - crate::omf::SegmentAlignment::Page4K => 4096, - _ => 1, - } + self.segment().align_bytes() } fn file_range(&self) -> Option<(u64, u64)> { + // OMF section data is not contiguous in the file. None } fn data(&self) -> Result<&'data [u8]> { - let segment = self.segment(); - - // Check if we have a single contiguous chunk that doesn't need expansion - if let Some(data) = segment.get_single_chunk() { - return Ok(data); - } - - // If we have no chunks, return empty slice - if segment.data_chunks.is_empty() { - return Ok(&[]); - } - - // For multiple chunks, LIDATA, or non-contiguous data, we can't return a reference - Err(Error( - "OMF segment data is not contiguous; use uncompressed_data() instead", - )) + self.segment().data() } fn data_range(&self, address: u64, size: u64) -> Result> { @@ -91,31 +72,18 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, .checked_add(size as usize) .ok_or(Error("Invalid data range"))?; - // Check if we have a single contiguous chunk that covers the range - if let Some(data) = segment.get_single_chunk() { - if offset > data.len() || end > data.len() { - return Ok(None); - } - return Ok(Some(&data[offset..end])); - } - - // For multiple chunks, check if the requested range is within a single chunk + // Check if the requested range is within a single direct chunk. for (chunk_offset, chunk) in &segment.data_chunks { let chunk_start = *chunk_offset as usize; - - // Only handle direct data chunks for now if let OmfDataChunk::Direct(chunk_data) = chunk { let chunk_end = chunk_start + chunk_data.len(); - if offset >= chunk_start && end <= chunk_end { - let relative_offset = offset - chunk_start; - let relative_end = end - chunk_start; - return Ok(Some(&chunk_data[relative_offset..relative_end])); + return Ok(Some(&chunk_data[offset - chunk_start..end - chunk_start])); } } } - // Range spans multiple chunks, includes LIDATA, or is not available + // Range spans multiple chunks, includes iterated data, or is not available. Ok(None) } @@ -129,59 +97,22 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, fn uncompressed_data(&self) -> Result> { let segment = self.segment(); - - // Check if we have a single contiguous chunk that doesn't need expansion - if let Some(data) = segment.get_single_chunk() { - return Ok(Cow::Borrowed(data)); - } - - // If we have no chunks, return empty if segment.data_chunks.is_empty() { return Ok(Cow::Borrowed(&[])); } - - // We need to construct the full segment data - let mut result = vec![0u8; segment.length as usize]; - - for (offset, chunk) in &segment.data_chunks { - let start = *offset as usize; - - match chunk { - OmfDataChunk::Direct(data) => { - // Direct data - let end = start + data.len(); - if end <= result.len() { - result[start..end].copy_from_slice(data); - } else { - return Err(Error("OMF segment data chunk exceeds segment length")); - } - } - OmfDataChunk::Iterated(lidata) => { - // LIDATA needs expansion - if let Ok(expanded) = expand_lidata_block(lidata) { - let end = start + expanded.len(); - if end <= result.len() { - result[start..end].copy_from_slice(&expanded); - } else { - return Err(Error("OMF LIDATA expansion exceeds segment length")); - } - } - } - } + if let Some(data) = segment.single_chunk() { + return Ok(Cow::Borrowed(data)); } - - Ok(Cow::Owned(result)) + // The data is non-contiguous or iterated, so it must be copied. + segment.build_data().map(Cow::Owned) } fn name_bytes(&self) -> Result<&'data [u8]> { - let segment = self.segment(); - self.file - .get_name(segment.name_index) - .ok_or(Error("Invalid segment name index")) + Ok(self.segment().name) } fn name(&self) -> Result<&'data str> { - str::from_utf8(self.name_bytes()?).map_err(|_| Error("Invalid UTF-8 in segment name")) + str::from_utf8(self.name_bytes()?).map_err(|_| Error("Invalid UTF-8 in OMF section name")) } fn segment_name_bytes(&self) -> Result> { @@ -193,13 +124,13 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, } fn kind(&self) -> SectionKind { - self.file.segment_section_kind(self.index) + self.file.section_kind(self.index) } fn relocations(&self) -> Self::RelocationIterator { OmfRelocationIterator { file: self.file, - segment_index: self.index, + section_index: self.index, index: 0, } } @@ -209,25 +140,11 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, } fn flags(&self) -> SectionFlags { - let segment = self.segment(); - let flags = SectionFlags::None; - - // Set flags based on segment properties - match segment.combination { - crate::omf::SegmentCombination::Public => { - // Public segments are like COMDAT sections - } - crate::omf::SegmentCombination::Stack => { - // Stack segments - } - _ => {} - } - - flags + SectionFlags::None } } -/// An iterator over OMF sections. +/// An iterator for the sections in an [`OmfFile`]. #[derive(Debug)] pub struct OmfSectionIterator<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>, @@ -238,7 +155,7 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSectionIterator<'data, 'fi type Item = OmfSection<'data, 'file, R>; fn next(&mut self) -> Option { - if self.index < self.file.segments.len() { + if self.index < self.file.sections.len() { let section = OmfSection { file: self.file, index: self.index, diff --git a/src/read/omf/segment.rs b/src/read/omf/segment.rs index 79304b4..3c6555f 100644 --- a/src/read/omf/segment.rs +++ b/src/read/omf/segment.rs @@ -1,75 +1,207 @@ use alloc::vec::Vec; -use crate::read::{self, ObjectSegment, ReadRef, Result, SectionKind}; +use crate::read::{self, Error, ObjectSegment, ReadRef, Result, SectionKind}; use crate::{omf, Permissions, SegmentFlags}; -use super::{OmfFile, OmfFixup}; +use super::{expand_iterated_data, iterated_data_expanded_len, OmfFile, OmfFixup}; -/// An OMF segment definition +/// A section in an OMF file. +/// +/// This is either a segment from a SEGDEF record, or a section synthesized +/// from COMDAT records. #[derive(Debug, Clone)] pub struct OmfSegment<'data> { - /// Segment name index (into names table) + /// Segment name. + /// + /// For sections synthesized from COMDAT or Borland COMDEF records, this + /// is the symbol name. + pub(super) name: &'data [u8], + /// Class name (resolved from the names table) + pub(super) class: &'data [u8], + /// Segment name index (into names table), or 0 if the name doesn't come + /// from the names table. pub(super) name_index: u16, /// Class name index (into names table) pub(super) class_index: u16, /// Overlay name index (into names table) - #[allow(unused)] // TODO pub(super) overlay_index: u16, /// Segment alignment pub(super) alignment: omf::SegmentAlignment, /// Segment combination pub(super) combination: omf::SegmentCombination, /// Whether this is a 32-bit segment - #[allow(unused)] // TODO pub(super) use32: bool, - /// Segment length - pub(super) length: u32, + /// Segment length in bytes + pub(super) length: u64, + /// Section kind for COMDAT sections with non-explicit allocation + pub(super) kind: Option, + /// Whether this section was synthesized from COMDAT records + pub(super) comdat: bool, /// Segment data chunks (offset, data) - /// Multiple LEDATA/LIDATA records can contribute to a single segment + /// + /// Multiple LEDATA/LIDATA/COMDAT records can contribute to a single section. pub(super) data_chunks: Vec<(u32, OmfDataChunk<'data>)>, - /// Relocations for this segment + /// Relocations for this section pub(super) relocations: Vec, } -/// Data chunk for a segment +/// Data chunk for a section #[derive(Debug, Clone)] pub(super) enum OmfDataChunk<'data> { - /// Direct data from LEDATA record + /// Direct data from a LEDATA or COMDAT record Direct(&'data [u8]), - /// Compressed/iterated data from LIDATA record (needs expansion) - Iterated(&'data [u8]), + /// Iterated data from a LIDATA or COMDAT record (needs expansion) + Iterated { + data: &'data [u8], + /// Whether repeat counts are 32-bit (from a 32-bit record type) + is_32bit: bool, + }, +} + +impl<'data> OmfDataChunk<'data> { + /// Get the size of the chunk after expansion + pub(super) fn expanded_len(&self) -> Result { + match *self { + OmfDataChunk::Direct(data) => Ok(data.len() as u64), + OmfDataChunk::Iterated { data, is_32bit } => iterated_data_expanded_len(data, is_32bit), + } + } } impl<'data> OmfSegment<'data> { - /// Get the raw data of the segment if it's a single contiguous chunk - pub fn get_single_chunk(&self) -> Option<&'data [u8]> { - if self.data_chunks.len() == 1 { - let (offset, chunk) = &self.data_chunks[0]; - if *offset == 0 { - match chunk { - OmfDataChunk::Direct(data) if data.len() == self.length as usize => { - return Some(data); - } - _ => {} - } + /// Get the segment name. + pub fn name(&self) -> &'data [u8] { + self.name + } + + /// Get the class name. + pub fn class(&self) -> &'data [u8] { + self.class + } + + /// Get the segment name index (into the names table). + /// + /// For COMDAT sections, this is the public name index. This is 0 for + /// sections whose name doesn't come from the names table. + pub fn name_index(&self) -> u16 { + self.name_index + } + + /// Get the class name index (into the names table). + /// + /// This is 0 for COMDAT sections without a base segment. + pub fn class_index(&self) -> u16 { + self.class_index + } + + /// Get the overlay name index (into the names table). + pub fn overlay_index(&self) -> u16 { + self.overlay_index + } + + /// Get the segment alignment. + pub fn alignment(&self) -> omf::SegmentAlignment { + self.alignment + } + + /// Get the segment combination. + pub fn combination(&self) -> omf::SegmentCombination { + self.combination + } + + /// Return true if this is a 32-bit segment. + pub fn use32(&self) -> bool { + self.use32 + } + + /// Get the segment length in bytes. + pub fn length(&self) -> u64 { + self.length + } + + /// Return true if this section was synthesized from COMDAT records. + pub fn is_comdat(&self) -> bool { + self.comdat + } + + /// Get the alignment in bytes. + pub(super) fn align_bytes(&self) -> u64 { + match self.alignment { + omf::SegmentAlignment::Absolute => 1, + omf::SegmentAlignment::Byte => 1, + omf::SegmentAlignment::Word => 2, + omf::SegmentAlignment::Paragraph => 16, + omf::SegmentAlignment::Page => 256, + omf::SegmentAlignment::DWord => 4, + omf::SegmentAlignment::Page4K => 4096, + } + } + + /// Get the raw data of the section if it's a single contiguous chunk + pub(super) fn single_chunk(&self) -> Option<&'data [u8]> { + if let [(0, OmfDataChunk::Direct(data))] = self.data_chunks[..] { + if data.len() as u64 == self.length { + return Some(data); } } None } - /// Check if any data chunk needs expansion (LIDATA) - pub fn has_iterated_data(&self) -> bool { - self.data_chunks - .iter() - .any(|(_, chunk)| matches!(chunk, OmfDataChunk::Iterated(_))) + /// Get the data of the section if it can be returned without copying. + /// + /// Returns an error if the data is non-contiguous or requires expansion. + pub(super) fn data(&self) -> Result<&'data [u8]> { + if self.data_chunks.is_empty() { + return Ok(&[]); + } + self.single_chunk().ok_or(Error( + "OMF section data is not contiguous; use uncompressed_data() instead", + )) + } + + /// Build the complete section data, expanding iterated data if needed. + pub(super) fn build_data(&self) -> Result> { + let length = usize::try_from(self.length).map_err(|_| Error("OMF section too large"))?; + let mut result = alloc::vec![0u8; length]; + + for (offset, chunk) in &self.data_chunks { + let start = *offset as usize; + match chunk { + OmfDataChunk::Direct(data) => { + let end = start + data.len(); + if end > result.len() { + return Err(Error("OMF section data chunk exceeds section length")); + } + result[start..end].copy_from_slice(data); + } + OmfDataChunk::Iterated { data, is_32bit } => { + let expanded = expand_iterated_data(data, *is_32bit)?; + let end = start + expanded.len(); + if end > result.len() { + return Err(Error("OMF section data chunk exceeds section length")); + } + result[start..end].copy_from_slice(&expanded); + } + } + } + + Ok(result) } } -/// An OMF segment reference. +/// A loadable section in an [`OmfFile`]. +/// +/// Most functionality is provided by the [`ObjectSegment`] trait implementation. #[derive(Debug)] pub struct OmfSegmentRef<'data, 'file, R: ReadRef<'data>> { - file: &'file OmfFile<'data, R>, - index: usize, + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> OmfSegmentRef<'data, 'file, R> { + fn segment(&self) -> &'file OmfSegment<'data> { + &self.file.sections[self.index] + } } impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for OmfSegmentRef<'data, 'file, R> {} @@ -80,47 +212,33 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSegment<'data> for OmfSegmentRef<'da } fn size(&self) -> u64 { - self.file.segments[self.index].length as u64 + self.segment().length } fn align(&self) -> u64 { - match self.file.segments[self.index].alignment { - crate::omf::SegmentAlignment::Byte => 1, - crate::omf::SegmentAlignment::Word => 2, - crate::omf::SegmentAlignment::Paragraph => 16, - crate::omf::SegmentAlignment::Page => 256, - crate::omf::SegmentAlignment::DWord => 4, - crate::omf::SegmentAlignment::Page4K => 4096, - _ => 1, - } + self.segment().align_bytes() } fn file_range(&self) -> (u64, u64) { + // OMF section data is not contiguous in the file. (0, 0) } fn data(&self) -> Result<&'data [u8]> { - // OMF segments don't have direct file mapping - Ok(&[]) + self.segment().data() } fn data_range(&self, _address: u64, _size: u64) -> Result> { + // OMF sections have no address. Ok(None) } fn name_bytes(&self) -> Result> { - Ok(self - .file - .get_name(self.file.segments[self.index].name_index)) + Ok(Some(self.segment().name)) } fn name(&self) -> Result> { - let index = self.file.segments[self.index].name_index; - let name_opt = self.file.get_name(index); - match name_opt { - Some(bytes) => Ok(core::str::from_utf8(bytes).ok()), - None => Ok(None), - } + Ok(core::str::from_utf8(self.segment().name).ok()) } fn flags(&self) -> SegmentFlags { @@ -129,8 +247,8 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSegment<'data> for OmfSegmentRef<'da fn permissions(&self) -> Permissions { // OMF segment definitions don't carry permission flags, so derive them - // from the segment's class name. - match self.file.segment_section_kind(self.index) { + // from the section kind. + match self.file.section_kind(self.index) { SectionKind::Text => Permissions::new(true, false, true), SectionKind::ReadOnlyData | SectionKind::ReadOnlyString | SectionKind::Debug => { Permissions::new(true, false, false) @@ -140,7 +258,7 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSegment<'data> for OmfSegmentRef<'da } } -/// An iterator over OMF segments. +/// An iterator for the loadable sections in an [`OmfFile`]. #[derive(Debug)] pub struct OmfSegmentIterator<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>, @@ -151,7 +269,7 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSegmentIterator<'data, 'fi type Item = OmfSegmentRef<'data, 'file, R>; fn next(&mut self) -> Option { - if self.index < self.file.segments.len() { + if self.index < self.file.sections.len() { let segment = OmfSegmentRef { file: self.file, index: self.index, diff --git a/src/read/omf/symbol.rs b/src/read/omf/symbol.rs index b342cc4..e9848de 100644 --- a/src/read/omf/symbol.rs +++ b/src/read/omf/symbol.rs @@ -7,30 +7,34 @@ use crate::read::{ use super::OmfFile; -/// An OMF symbol +/// A symbol in an [`OmfFile`]. +/// +/// Most functionality is provided by the [`ObjectSymbol`] trait implementation. #[derive(Debug, Clone)] pub struct OmfSymbol<'data> { /// Symbol table index - pub symbol_index: usize, + pub(super) index: SymbolIndex, /// Symbol name - pub name: &'data [u8], + pub(super) name: &'data [u8], /// Symbol class (Public, External, etc.) - pub class: OmfSymbolClass, - /// Group index (0 if none) - pub group_index: u16, - /// Segment index (0 if external) - pub segment_index: u16, - /// Frame number (for absolute symbols when segment_index == 0) - pub frame_number: u16, - /// Offset within segment - pub offset: u32, + pub(super) class: OmfSymbolClass, + /// The section that defines this symbol, if any + pub(super) section: Option, + /// Whether this is an absolute symbol (PUBDEF with segment index 0) + pub(super) absolute: bool, + /// Frame number (for absolute symbols) + pub(super) frame_number: u16, + /// Offset within the section + pub(super) offset: u64, + /// Symbol size (communal length for COMDEF, section size for COMDAT) + pub(super) size: u64, /// Type index (usually 0) - pub type_index: u16, + pub(super) type_index: u16, /// Pre-computed symbol kind - pub kind: SymbolKind, + pub(super) kind: SymbolKind, } -/// Symbol class for OMF symbols +/// The kind of OMF record that defined a symbol. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OmfSymbolClass { /// Public symbol (PUBDEF) @@ -47,13 +51,35 @@ pub enum OmfSymbolClass { LocalCommunal, /// COMDAT external symbol (CEXTDEF) ComdatExternal, + /// COMDAT symbol (COMDAT) + Comdat, + /// Local COMDAT symbol (COMDAT with the local flag) + LocalComdat, +} + +impl<'data> OmfSymbol<'data> { + /// Get the symbol class, which corresponds to the kind of OMF record + /// that defined the symbol. + pub fn class(&self) -> OmfSymbolClass { + self.class + } + + /// Get the type index. + pub fn type_index(&self) -> u16 { + self.type_index + } + + /// Get the frame number for absolute symbols. + pub fn frame_number(&self) -> u16 { + self.frame_number + } } impl<'data> read::private::Sealed for OmfSymbol<'data> {} impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { fn index(&self) -> SymbolIndex { - SymbolIndex(self.symbol_index) + self.index } fn name_bytes(&self) -> Result<&'data [u8]> { @@ -61,21 +87,21 @@ impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { } fn name(&self) -> Result<&'data str> { - core::str::from_utf8(self.name).map_err(|_| Error("Invalid UTF-8 in OMF symbol name")) + str::from_utf8(self.name).map_err(|_| Error("Invalid UTF-8 in OMF symbol name")) } fn address(&self) -> u64 { - if self.segment_index == 0 && self.frame_number != 0 { - // For absolute symbols, compute the linear address from frame:offset - // Frame number is in paragraphs (16-byte units) - ((self.frame_number as u64) << 4) + (self.offset as u64) + if self.absolute { + // For absolute symbols, compute the linear address from frame:offset. + // Frame number is in paragraphs (16-byte units). + ((self.frame_number as u64) << 4) + self.offset } else { - self.offset as u64 + self.offset } } fn size(&self) -> u64 { - 0 // OMF doesn't store symbol sizes + self.size } fn kind(&self) -> SymbolKind { @@ -83,30 +109,37 @@ impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { } fn section(&self) -> SymbolSection { - if self.segment_index == 0 { - if self.frame_number != 0 { - SymbolSection::Absolute - } else { - SymbolSection::Undefined - } + if let Some(section) = self.section { + SymbolSection::Section(section) + } else if self.absolute { + SymbolSection::Absolute + } else if self.is_common() { + SymbolSection::Common } else { - SymbolSection::Section(SectionIndex(self.segment_index as usize)) + SymbolSection::Undefined } } fn is_undefined(&self) -> bool { - self.segment_index == 0 && self.frame_number == 0 + matches!( + self.class, + OmfSymbolClass::External + | OmfSymbolClass::LocalExternal + | OmfSymbolClass::ComdatExternal + ) } fn is_definition(&self) -> bool { - self.segment_index != 0 || self.frame_number != 0 + self.section.is_some() || self.absolute } fn is_common(&self) -> bool { + // Borland communal symbols with a virtual segment are defined in a + // section, so they are not common. matches!( self.class, - super::OmfSymbolClass::Communal | super::OmfSymbolClass::LocalCommunal - ) + OmfSymbolClass::Communal | OmfSymbolClass::LocalCommunal + ) && self.section.is_none() } fn is_weak(&self) -> bool { @@ -115,19 +148,14 @@ impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { fn scope(&self) -> SymbolScope { match self.class { - super::OmfSymbolClass::LocalPublic - | super::OmfSymbolClass::LocalExternal - | super::OmfSymbolClass::LocalCommunal => SymbolScope::Compilation, - super::OmfSymbolClass::Public - | super::OmfSymbolClass::External - | super::OmfSymbolClass::Communal - | super::OmfSymbolClass::ComdatExternal => { - if self.segment_index == 0 { - SymbolScope::Unknown - } else { - SymbolScope::Linkage - } + OmfSymbolClass::LocalPublic + | OmfSymbolClass::LocalExternal + | OmfSymbolClass::LocalCommunal + | OmfSymbolClass::LocalComdat => SymbolScope::Compilation, + OmfSymbolClass::Public | OmfSymbolClass::Communal | OmfSymbolClass::Comdat => { + SymbolScope::Linkage } + OmfSymbolClass::External | OmfSymbolClass::ComdatExternal => SymbolScope::Unknown, } } @@ -138,9 +166,10 @@ impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { fn is_local(&self) -> bool { matches!( self.class, - super::OmfSymbolClass::LocalPublic - | super::OmfSymbolClass::LocalExternal - | super::OmfSymbolClass::LocalCommunal + OmfSymbolClass::LocalPublic + | OmfSymbolClass::LocalExternal + | OmfSymbolClass::LocalCommunal + | OmfSymbolClass::LocalComdat ) } @@ -149,7 +178,7 @@ impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { } } -/// An iterator over OMF symbols. +/// An iterator for the symbols in an [`OmfFile`]. #[derive(Debug)] pub struct OmfSymbolIterator<'data, 'file, R: ReadRef<'data> = &'data [u8]> { pub(super) file: &'file OmfFile<'data, R>, @@ -166,7 +195,7 @@ impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSymbolIterator<'data, 'fil } } -/// An OMF symbol table. +/// A symbol table in an [`OmfFile`]. #[derive(Debug)] pub struct OmfSymbolTable<'data, 'file, R: ReadRef<'data>> { pub(super) file: &'file OmfFile<'data, R>,