diff --git a/Cargo.toml b/Cargo.toml index 37366a29..a6eda404 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ alloc = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-all # Core read support. You will need to enable some file formats too. read_core = [] # Read support for most file formats (including unaligned files). -read = ["read_core", "archive", "coff", "elf", "macho", "pe", "xcoff", "unaligned"] +read = ["read_core", "archive", "coff", "elf", "macho", "pe", "xcoff", "omf", "unaligned"] # Core write support. You will need to enable some file formats too. write_core = ["dep:crc32fast", "dep:indexmap", "dep:hashbrown"] # Core write support with libstd features. You will need to enable some file formats too. @@ -81,6 +81,7 @@ macho = [] pe = ["coff"] wasm = ["dep:wasmparser"] xcoff = [] +omf = [] #======================================= # By default, support all read features. @@ -89,7 +90,7 @@ default = ["read", "compression"] #======================================= # Umbrella feature for enabling all user-facing features of this crate. Does not # enable internal features like `rustc-dep-of-std`. -all = ["read", "write", "build", "std", "compression", "wasm"] +all = ["read", "write", "build", "std", "compression", "wasm", "omf"] # Use of --all-features is not supported. # This is a dummy feature to detect when --all-features is used. @@ -100,7 +101,7 @@ cargo-all = [] doc = [ "read_core", "write_std", "build_core", "std", "compression", - "archive", "coff", "elf", "macho", "pe", "wasm", "xcoff", + "archive", "coff", "elf", "macho", "pe", "wasm", "xcoff", "omf", ] #======================================= diff --git a/src/build/elf.rs b/src/build/elf.rs index 9ade35ca..df8ae7a7 100644 --- a/src/build/elf.rs +++ b/src/build/elf.rs @@ -434,10 +434,10 @@ impl<'data> Builder<'data> { ) .map(SectionData::Relocation) } else { - return Err(Error(format!( + Err(Error(format!( "Invalid sh_link {} in relocation section at index {}", link.0, index, - ))); + ))) } } diff --git a/src/common.rs b/src/common.rs index d0a4cdbb..81241ee1 100644 --- a/src/common.rs +++ b/src/common.rs @@ -132,6 +132,7 @@ pub enum BinaryFormat { Pe, Wasm, Xcoff, + Omf, } impl BinaryFormat { @@ -616,4 +617,16 @@ pub enum RelocationFlags { /// `r_rsize` field in the XCOFF relocation. r_rsize: u8, }, + #[cfg(feature = "omf")] + /// OMF relocation metadata. + Omf { + /// The location field describing what bytes are being fixed up. + location: crate::omf::FixupLocation, + /// Whether the relocation is applied segment-relative (`M = 1`) or self-relative (`M = 0`). + mode: crate::omf::FixupMode, + /// The frame datum used to establish the base reference for the relocation. + frame: crate::omf::FixupFrame, + /// The target datum identifying the entity being referenced. + target: crate::omf::FixupTarget, + }, } diff --git a/src/lib.rs b/src/lib.rs index 9a2fdd41..41ed0770 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,8 @@ pub mod archive; pub mod elf; #[cfg(feature = "macho")] pub mod macho; +#[cfg(feature = "omf")] +pub mod omf; #[cfg(any(feature = "coff", feature = "pe"))] pub mod pe; #[cfg(feature = "xcoff")] diff --git a/src/omf.rs b/src/omf.rs new file mode 100644 index 00000000..073f8f5d --- /dev/null +++ b/src/omf.rs @@ -0,0 +1,295 @@ +//! Object Module Format (OMF) definitions for classic DOS object files. +//! +//! This module provides type definitions and constants for working with OMF files, +//! as defined in the TIS Relocatable Object Module Format (OMF) Specification v1.1. +//! +//! OMF was commonly used by DOS compilers like Borland C++ and Watcom C. + +use crate::endian::U16; +use crate::pod::Pod; + +/// OMF record type constants +pub mod record_type { + /// Translator Header Record + pub const THEADR: u8 = 0x80; + /// Library Module Header Record + pub const LHEADR: u8 = 0x82; + /// Comment Record + pub const COMENT: u8 = 0x88; + /// Module End Record (16-bit) + pub const MODEND: u8 = 0x8A; + /// Module End Record (32-bit) + pub const MODEND32: u8 = 0x8B; + /// External Names Definition Record + pub const EXTDEF: u8 = 0x8C; + /// Type Definition Record (obsolete) + pub const TYPDEF: u8 = 0x8E; + /// Public Names Definition Record (16-bit) + pub const PUBDEF: u8 = 0x90; + /// Public Names Definition Record (32-bit) + pub const PUBDEF32: u8 = 0x91; + /// Line Numbers Record (16-bit) + pub const LINNUM: u8 = 0x94; + /// Line Numbers Record (32-bit) + pub const LINNUM32: u8 = 0x95; + /// List of Names Record + pub const LNAMES: u8 = 0x96; + /// Segment Definition Record (16-bit) + pub const SEGDEF: u8 = 0x98; + /// Segment Definition Record (32-bit) + pub const SEGDEF32: u8 = 0x99; + /// Group Definition Record + pub const GRPDEF: u8 = 0x9A; + /// Fixup Record (16-bit) + pub const FIXUPP: u8 = 0x9C; + /// Fixup Record (32-bit) + pub const FIXUPP32: u8 = 0x9D; + /// Logical Enumerated Data Record (16-bit) + pub const LEDATA: u8 = 0xA0; + /// Logical Enumerated Data Record (32-bit) + pub const LEDATA32: u8 = 0xA1; + /// Logical Iterated Data Record (16-bit) + pub const LIDATA: u8 = 0xA2; + /// Logical Iterated Data Record (32-bit) + pub const LIDATA32: u8 = 0xA3; + /// Communal Names Definition Record + pub const COMDEF: u8 = 0xB0; + /// Backpatch Record (16-bit) + pub const BAKPAT: u8 = 0xB2; + /// Backpatch Record (32-bit) + pub const BAKPAT32: u8 = 0xB3; + /// Local External Names Definition Record (16-bit) + pub const LEXTDEF: u8 = 0xB4; + /// Local External Names Definition Record (32-bit) + pub const LEXTDEF32: u8 = 0xB5; + /// Local Public Names Definition Record (16-bit) + pub const LPUBDEF: u8 = 0xB6; + /// Local Public Names Definition Record (32-bit) + pub const LPUBDEF32: u8 = 0xB7; + /// Local Communal Names Definition Record + pub const LCOMDEF: u8 = 0xB8; + /// COMDAT External Names Definition Record + pub const CEXTDEF: u8 = 0xBC; + /// Initialized Communal Data Record (16-bit) + pub const COMDAT: u8 = 0xC2; + /// Initialized Communal Data Record (32-bit) + pub const COMDAT32: u8 = 0xC3; + /// Symbol Line Numbers Record (16-bit) + pub const LINSYM: u8 = 0xC4; + /// Symbol Line Numbers Record (32-bit) + pub const LINSYM32: u8 = 0xC5; + /// Alias Definition Record + pub const ALIAS: u8 = 0xC6; + /// Named Backpatch Record (16-bit) + pub const NBKPAT: u8 = 0xC8; + /// Named Backpatch Record (32-bit) + pub const NBKPAT32: u8 = 0xC9; + /// Local Logical Names Definition Record + pub const LLNAMES: u8 = 0xCA; + /// OMF Version Number Record + pub const VERNUM: u8 = 0xCC; + /// Vendor-specific OMF Extension Record + pub const VENDEXT: u8 = 0xCE; + + /// Return true if the record type is valid + pub fn is_valid(record_type: u8) -> bool { + matches!( + record_type, + THEADR + | LHEADR + | COMENT + | MODEND + | MODEND32 + | EXTDEF + | TYPDEF + | PUBDEF + | PUBDEF32 + | LINNUM + | LINNUM32 + | LNAMES + | SEGDEF + | SEGDEF32 + | GRPDEF + | FIXUPP + | FIXUPP32 + | LEDATA + | LEDATA32 + | LIDATA + | LIDATA32 + | COMDEF + | BAKPAT + | BAKPAT32 + | LEXTDEF + | LEXTDEF32 + | LPUBDEF + | LPUBDEF32 + | LCOMDEF + | CEXTDEF + | COMDAT + | COMDAT32 + | LINSYM + | LINSYM32 + | ALIAS + | NBKPAT + | NBKPAT32 + | LLNAMES + | VERNUM + | VENDEXT + ) + } +} + +/// The addressing mode for an OMF relocation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum FixupMode { + /// Self-relative relocation (`M = 0`). + SelfRelative = 0, + /// Segment-relative relocation (`M = 1`). + SegmentRelative = 1, +} + +/// Frame datum variants as defined by the OMF specification. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FixupFrame { + /// Segment frame datum referencing a 1-based segment index. + Segment(u16), + /// Group frame datum referencing a 1-based group index. + Group(u16), + /// External frame datum referencing a 1-based entry in the external-name table. + External(u16), + /// Explicit frame number datum. + FrameNumber(u16), + /// Use the location of the fixup as the frame datum. + Location, + /// Use the target's frame datum. + Target, +} + +/// Target datum variants as defined by the OMF specification. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FixupTarget { + /// Segment target datum referencing a 1-based segment index. + Segment(u16), + /// Group target datum referencing a 1-based group index. + Group(u16), + /// External target datum referencing a 1-based entry in the external-name table. + External(u16), + /// Explicit frame number datum. + FrameNumber(u16), +} + +/// OMF record header +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct RecordHeader { + /// Record type identifier + pub record_type: u8, + /// Length of the record contents (excluding header and checksum) + pub length: U16, +} + +unsafe impl Pod for RecordHeader {} + +/// Segment alignment types for SEGDEF records +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SegmentAlignment { + /// Absolute segment + Absolute = 0, + /// Byte aligned + Byte = 1, + /// Word (2-byte) aligned + Word = 2, + /// Paragraph (16-byte) aligned + Paragraph = 3, + /// Page (256-byte) aligned + Page = 4, + /// Double word (4-byte) aligned + DWord = 5, + /// 4K page aligned + Page4K = 6, +} + +/// Segment combination types for SEGDEF records +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SegmentCombination { + /// Private segment + Private = 0, + /// Public segment (concatenated) + Public = 2, + /// Stack segment + Stack = 5, + /// Common segment (overlapped) + Common = 6, +} + +/// Fixup location types +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum FixupLocation { + /// Low-order byte + LowByte = 0, + /// 16-bit offset + Offset = 1, + /// 16-bit base/segment + Base = 2, + /// 32-bit pointer (16:16) + Pointer = 3, + /// High-order byte + HighByte = 4, + /// 16-bit loader-resolved offset + LoaderOffset = 5, + /// 32-bit offset + Offset32 = 9, + /// 48-bit pointer (16:32) + Pointer48 = 11, + /// 32-bit loader-resolved offset + LoaderOffset32 = 13, +} + +/// Return true if the data looks like an OMF file. +pub(crate) fn is_omf<'data, R: crate::ReadRef<'data>>(data: R, offset: u64) -> bool { + let Ok(header) = data.read_at::(offset) else { + return false; + }; + if !matches!( + header.record_type, + record_type::THEADR | record_type::LHEADR + ) { + return false; + } + let length = header.length.get(crate::endian::LittleEndian) as usize; + if length < 1 { + return false; + } + // Read the full record including the checksum byte + let Ok(record) = data.read_bytes_at(offset, (3 + length) as u64) else { + return false; + }; + // Verify the record checksum + if !verify_checksum(record) { + return false; + } + // Check that the translator or module name string fits in the record + if length > 1 { + let name_len = record[3] as usize; + if name_len > length - 1 { + return false; + } + } + true +} + +/// Verify the checksum of an OMF record +/// +/// The checksum is calculated so that the sum of all bytes in the record, +/// including the checksum byte itself, equals 0 (modulo 256). +/// +/// Some compilers write 0 rather than computing the checksum, +/// so we accept that as valid. +pub(crate) fn verify_checksum(record: &[u8]) -> bool { + let checksum = record.last().copied().unwrap_or(0); + checksum == 0 || record.iter().copied().fold(0u8, u8::wrapping_add) == 0 +} diff --git a/src/read/any.rs b/src/read/any.rs index d6408ad4..e7492fcd 100644 --- a/src/read/any.rs +++ b/src/read/any.rs @@ -10,6 +10,8 @@ use crate::read::coff; use crate::read::elf; #[cfg(feature = "macho")] use crate::read::macho; +#[cfg(feature = "omf")] +use crate::read::omf; #[cfg(feature = "pe")] use crate::read::pe; #[cfg(feature = "wasm")] @@ -52,6 +54,8 @@ macro_rules! with_inner { $enum::Xcoff32(ref $var) => $body, #[cfg(feature = "xcoff")] $enum::Xcoff64(ref $var) => $body, + #[cfg(feature = "omf")] + $enum::Omf(ref $var) => $body, } }; } @@ -81,6 +85,8 @@ macro_rules! with_inner_mut { $enum::Xcoff32(ref mut $var) => $body, #[cfg(feature = "xcoff")] $enum::Xcoff64(ref mut $var) => $body, + #[cfg(feature = "omf")] + $enum::Omf(ref mut $var) => $body, } }; } @@ -111,6 +117,8 @@ macro_rules! map_inner { $from::Xcoff32(ref $var) => $to::Xcoff32($body), #[cfg(feature = "xcoff")] $from::Xcoff64(ref $var) => $to::Xcoff64($body), + #[cfg(feature = "omf")] + $from::Omf(ref $var) => $to::Omf($body), } }; } @@ -141,6 +149,8 @@ macro_rules! map_inner_option { $from::Xcoff32(ref $var) => $body.map($to::Xcoff32), #[cfg(feature = "xcoff")] $from::Xcoff64(ref $var) => $body.map($to::Xcoff64), + #[cfg(feature = "omf")] + $from::Omf(ref $var) => $body.map($to::Omf), } }; } @@ -170,6 +180,8 @@ macro_rules! map_inner_option_mut { $from::Xcoff32(ref mut $var) => $body.map($to::Xcoff32), #[cfg(feature = "xcoff")] $from::Xcoff64(ref mut $var) => $body.map($to::Xcoff64), + #[cfg(feature = "omf")] + $from::Omf(ref mut $var) => $body.map($to::Omf), } }; } @@ -200,6 +212,8 @@ macro_rules! next_inner { $from::Xcoff32(ref mut iter) => iter.next().map($to::Xcoff32), #[cfg(feature = "xcoff")] $from::Xcoff64(ref mut iter) => iter.next().map($to::Xcoff64), + #[cfg(feature = "omf")] + $from::Omf(ref mut iter) => iter.next().map($to::Omf), } }; } @@ -233,6 +247,8 @@ pub enum File<'data, R: ReadRef<'data> = &'data [u8]> { Xcoff32(xcoff::XcoffFile32<'data, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffFile64<'data, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfFile<'data, R>), } impl<'data, R: ReadRef<'data>> File<'data, R> { @@ -261,6 +277,8 @@ impl<'data, R: ReadRef<'data>> File<'data, R> { FileKind::Xcoff32 => File::Xcoff32(xcoff::XcoffFile32::parse(data)?), #[cfg(feature = "xcoff")] FileKind::Xcoff64 => File::Xcoff64(xcoff::XcoffFile64::parse(data)?), + #[cfg(feature = "omf")] + FileKind::Omf => File::Omf(omf::OmfFile::parse(data)?), #[allow(unreachable_patterns)] _ => return Err(Error("Unsupported file format")), }) @@ -297,6 +315,8 @@ impl<'data, R: ReadRef<'data>> File<'data, R> { File::Wasm(_) => BinaryFormat::Wasm, #[cfg(feature = "xcoff")] File::Xcoff32(_) | File::Xcoff64(_) => BinaryFormat::Xcoff, + #[cfg(feature = "omf")] + File::Omf(_) => BinaryFormat::Omf, } } } @@ -557,6 +577,8 @@ enum SegmentIteratorInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffSegmentIterator32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffSegmentIterator64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfSegmentIterator<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> Iterator for SegmentIterator<'data, 'file, R> { @@ -599,6 +621,8 @@ enum SegmentInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffSegment32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffSegment64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfSegmentRef<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> fmt::Debug for Segment<'data, 'file, R> { @@ -691,6 +715,8 @@ enum SectionIteratorInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffSectionIterator32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffSectionIterator64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfSectionIterator<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> Iterator for SectionIterator<'data, 'file, R> { @@ -732,6 +758,8 @@ enum SectionInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffSection32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffSection64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfSection<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> fmt::Debug for Section<'data, 'file, R> { @@ -798,6 +826,10 @@ impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for Section<'data, 'f with_inner!(self.inner, SectionInternal, |x| x.compressed_data()) } + fn uncompressed_data(&self) -> Result> { + with_inner!(self.inner, SectionInternal, |x| x.uncompressed_data()) + } + fn name_bytes(&self) -> Result<&'data [u8]> { with_inner!(self.inner, SectionInternal, |x| x.name_bytes()) } @@ -868,6 +900,8 @@ enum ComdatIteratorInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffComdatIterator32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffComdatIterator64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfComdatIterator<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> Iterator for ComdatIterator<'data, 'file, R> { @@ -909,6 +943,8 @@ enum ComdatInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffComdat32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffComdat64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfComdat<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> fmt::Debug for Comdat<'data, 'file, R> { @@ -984,6 +1020,8 @@ enum ComdatSectionIteratorInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffComdatSectionIterator32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffComdatSectionIterator64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfComdatSectionIterator<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> Iterator for ComdatSectionIterator<'data, 'file, R> { @@ -1052,6 +1090,8 @@ where Xcoff32((xcoff::XcoffSymbolTable32<'data, 'file, R>, PhantomData)), #[cfg(feature = "xcoff")] Xcoff64((xcoff::XcoffSymbolTable64<'data, 'file, R>, PhantomData)), + #[cfg(feature = "omf")] + Omf((omf::OmfSymbolTable<'data, 'file, R>, PhantomData)), } impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for SymbolTable<'data, 'file, R> {} @@ -1146,6 +1186,8 @@ where PhantomData, ), ), + #[cfg(feature = "omf")] + Omf((omf::OmfSymbolIterator<'data, 'file, R>, PhantomData)), } impl<'data, 'file, R: ReadRef<'data>> Iterator for SymbolIterator<'data, 'file, R> { @@ -1215,6 +1257,8 @@ where Xcoff32((xcoff::XcoffSymbol32<'data, 'file, R>, PhantomData)), #[cfg(feature = "xcoff")] Xcoff64((xcoff::XcoffSymbol64<'data, 'file, R>, PhantomData)), + #[cfg(feature = "omf")] + Omf((omf::OmfSymbol<'data>, PhantomData)), } impl<'data, 'file, R: ReadRef<'data>> fmt::Debug for Symbol<'data, 'file, R> { @@ -1363,6 +1407,8 @@ enum SectionRelocationIteratorInternal<'data, 'file, R: ReadRef<'data>> { Xcoff32(xcoff::XcoffRelocationIterator32<'data, 'file, R>), #[cfg(feature = "xcoff")] Xcoff64(xcoff::XcoffRelocationIterator64<'data, 'file, R>), + #[cfg(feature = "omf")] + Omf(omf::OmfRelocationIterator<'data, 'file, R>), } impl<'data, 'file, R: ReadRef<'data>> Iterator for SectionRelocationIterator<'data, 'file, R> { diff --git a/src/read/mod.rs b/src/read/mod.rs index 11d2e732..8d7aefe3 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -104,6 +104,9 @@ pub mod wasm; #[cfg(feature = "xcoff")] pub mod xcoff; +#[cfg(feature = "omf")] +pub mod omf; + mod traits; pub use traits::*; @@ -278,6 +281,11 @@ pub enum FileKind { /// See [`xcoff::XcoffFile64`]. #[cfg(feature = "xcoff")] Xcoff64, + /// An OMF object file. + /// + /// See [`omf::OmfFile`]. + #[cfg(feature = "omf")] + Omf, } impl FileKind { @@ -360,6 +368,11 @@ impl FileKind { [0x01, 0xdf, ..] => FileKind::Xcoff32, #[cfg(feature = "xcoff")] [0x01, 0xf7, ..] => FileKind::Xcoff64, + #[cfg(feature = "omf")] + [crate::omf::record_type::THEADR, ..] | [crate::omf::record_type::LHEADR, ..] + if crate::omf::is_omf(data, offset) => { + FileKind::Omf + } _ => return Err(Error("Unknown file magic")), }; Ok(kind) diff --git a/src/read/omf/comdat.rs b/src/read/omf/comdat.rs new file mode 100644 index 00000000..3d018537 --- /dev/null +++ b/src/read/omf/comdat.rs @@ -0,0 +1,128 @@ +use crate::read::{self, Error, Result}; +use crate::{omf, ComdatKind, ObjectComdat, ReadRef, SectionIndex, SymbolIndex}; + +use super::OmfFile; + +/// A COMDAT (communal data) section +#[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 + pub(super) selection: OmfComdatSelection, + /// Alignment + #[allow(unused)] + pub(super) alignment: omf::SegmentAlignment, + /// Data + #[allow(unused)] + pub(super) data: &'data [u8], +} + +/// COMDAT selection methods +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum OmfComdatSelection { + /// Explicit: may not be combined, produce error if multiple definitions + Explicit = 0, + /// Use any: pick any instance + UseAny = 1, + /// Same size: all instances must be same size + SameSize = 2, + /// Exact match: all instances must have identical content + ExactMatch = 3, +} + +/// A COMDAT section in an OMF file. +#[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> {} + +impl<'data, 'file, R: ReadRef<'data>> ObjectComdat<'data> for OmfComdat<'data, 'file, R> { + type SectionIterator = OmfComdatSectionIterator<'data, 'file, R>; + + fn kind(&self) -> ComdatKind { + let comdat = &self.file.comdats[self.index]; + match comdat.selection { + OmfComdatSelection::Explicit => ComdatKind::NoDuplicates, + OmfComdatSelection::UseAny => ComdatKind::Any, + OmfComdatSelection::SameSize => ComdatKind::SameSize, + OmfComdatSelection::ExactMatch => ComdatKind::ExactMatch, + } + } + + fn symbol(&self) -> SymbolIndex { + // COMDAT symbols don't have a direct symbol index in OMF + SymbolIndex(usize::MAX) + } + + fn name_bytes(&self) -> Result<&'data [u8]> { + let comdat = &self.file.comdats[self.index]; + Ok(comdat.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")) + } + + 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, + _phantom: core::marker::PhantomData, + } + } +} + +/// An iterator over COMDAT sections. +#[derive(Debug)] +pub struct OmfComdatIterator<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfComdatIterator<'data, 'file, R> { + type Item = OmfComdat<'data, 'file, R>; + + fn next(&mut self) -> Option { + if self.index < self.file.comdats.len() { + let comdat = OmfComdat { + file: self.file, + index: self.index, + _phantom: core::marker::PhantomData, + }; + self.index += 1; + Some(comdat) + } else { + None + } + } +} + +/// An iterator over sections in a COMDAT. +#[derive(Debug)] +pub struct OmfComdatSectionIterator<'data, 'file, R: ReadRef<'data>> { + segment_index: Option, + returned: bool, + _phantom: core::marker::PhantomData<(&'data (), &'file (), R)>, +} + +impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfComdatSectionIterator<'data, 'file, R> { + type Item = SectionIndex; + + fn next(&mut self) -> Option { + if !self.returned { + self.returned = true; + self.segment_index.map(|idx| SectionIndex(idx + 1)) + } else { + None + } + } +} diff --git a/src/read/omf/file.rs b/src/read/omf/file.rs new file mode 100644 index 00000000..cae07055 --- /dev/null +++ b/src/read/omf/file.rs @@ -0,0 +1,1586 @@ +//! OMF file implementation for the unified read API. + +use alloc::vec::Vec; + +use crate::read::{ + self, Architecture, ByteString, CodeView, Error, Export, FileFlags, Import, + NoDynamicRelocationIterator, Object, ObjectKind, ObjectSection, ReadRef, Result, SectionIndex, + SymbolIndex, +}; +use crate::{omf, SubArchitecture}; + +use super::{ + OmfComdat, OmfComdatData, OmfComdatIterator, OmfComdatSelection, OmfDataChunk, OmfFixup, + OmfGroup, OmfSection, OmfSectionIterator, OmfSegment, OmfSegmentIterator, OmfSegmentRef, + OmfSymbol, OmfSymbolClass, OmfSymbolIterator, OmfSymbolTable, +}; + +/// An OMF object file. +/// +/// This handles both 16-bit and 32-bit OMF variants. +#[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>, + /// All symbols (publics, externals, communals, locals) in occurrence order + pub(super) symbols: Vec>, + /// Maps external-name table index (1-based) to SymbolIndex + pub(super) external_order: Vec, + /// COMDAT sections + pub(super) comdats: Vec>, + /// Name table (LNAMES/LLNAMES) + pub(super) names: Vec<&'data [u8]>, + /// Group definitions + pub(super) groups: Vec, +} + +impl<'data, R: ReadRef<'data>> read::private::Sealed for OmfFile<'data, R> {} + +impl<'data, R: ReadRef<'data>> OmfFile<'data, R> { + /// Parse an OMF file from raw data + pub fn parse(data: R) -> Result { + let mut file = OmfFile { + data, + module_name: None, + segments: Vec::new(), + symbols: Vec::new(), + external_order: Vec::new(), + comdats: Vec::new(), + names: Vec::new(), + groups: Vec::new(), + }; + + file.parse_records()?; + file.assign_symbol_kinds(); + Ok(file) + } + + fn assign_symbol_kinds(&mut self) { + // Compute kinds for symbols based on their segments + let kinds: Vec = 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, + }) + .collect(); + + // Apply computed kinds + for (sym, kind) in self.symbols.iter_mut().zip(kinds) { + sym.kind = kind; + } + } + + fn symbol_kind_from_section_kind(section_kind: read::SectionKind) -> read::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, + } + } + + /// 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; + }; + + let segment_name = self.get_name(segment.name_index).unwrap_or_default(); + let class_name = self.get_name(segment.class_index).unwrap_or_default(); + + // Reserved names for debug sections + if segment_name.starts_with(b"$$") { + return read::SectionKind::Debug; + } + + // Substring matches for common class names + if class_name.windows(4).any(|w| w == b"CODE") { + return read::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; + } else { + return read::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; + } else if class_name.starts_with(b"DEB") { + return read::SectionKind::Debug; + } else if class_name == b"COMMON" { + return read::SectionKind::Common; + } + + read::SectionKind::Unknown + } + + fn parse_records(&mut self) -> Result<()> { + let mut current_segment: Option = None; + let mut current_data_offset: Option = None; + + // Thread storage for FIXUPP parsing + let mut frame_threads: [Option; 4] = [None; 4]; + let mut target_threads: [Option; 4] = [None; 4]; + + let mut offset = 0; + while let Ok(record_header) = self.data.read_at::(offset) { + let record_type = record_header.record_type; + let record_length = record_header.length.get(crate::endian::LittleEndian); + let record_data = self + .data + .read_bytes_at(offset, record_length as u64 + 3) + .map_err(|_| Error("Truncated OMF record data"))?; + + if offset == 0 + && !matches!( + record_type, + omf::record_type::THEADR | omf::record_type::LHEADR + ) + { + return Err(Error( + "Invalid OMF file: first record must be THEADR or LHEADR", + )); + } + + // Verify checksum + if !omf::verify_checksum(record_data) { + return Err(Error("Invalid OMF record checksum")); + } + + // Exclude the header and checksum + let inner_data = &record_data[3..2 + record_length as usize]; + + // Process record based on type + match record_type { + omf::record_type::THEADR | omf::record_type::LHEADR => { + self.parse_header(inner_data)?; + } + omf::record_type::LNAMES | omf::record_type::LLNAMES => { + self.parse_names(inner_data)?; + } + omf::record_type::SEGDEF | omf::record_type::SEGDEF32 => { + self.parse_segdef(inner_data, record_type == omf::record_type::SEGDEF32)?; + } + omf::record_type::GRPDEF => { + self.parse_grpdef(inner_data)?; + } + omf::record_type::PUBDEF | omf::record_type::PUBDEF32 => { + self.parse_pubdef( + inner_data, + record_type == omf::record_type::PUBDEF32, + OmfSymbolClass::Public, + )?; + } + omf::record_type::LPUBDEF | omf::record_type::LPUBDEF32 => { + self.parse_pubdef( + inner_data, + record_type == omf::record_type::LPUBDEF32, + OmfSymbolClass::LocalPublic, + )?; + } + omf::record_type::EXTDEF => { + self.parse_extdef(inner_data, OmfSymbolClass::External)?; + } + omf::record_type::LEXTDEF | omf::record_type::LEXTDEF32 => { + self.parse_extdef(inner_data, OmfSymbolClass::LocalExternal)?; + } + omf::record_type::CEXTDEF => { + self.parse_extdef(inner_data, OmfSymbolClass::ComdatExternal)?; + } + omf::record_type::COMDEF => { + self.parse_comdef(inner_data, OmfSymbolClass::Communal)?; + } + omf::record_type::LCOMDEF => { + 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)?; + } + omf::record_type::LEDATA | omf::record_type::LEDATA32 => { + let (seg_idx, offset) = + self.parse_ledata(inner_data, record_type == omf::record_type::LEDATA32)?; + current_segment = Some(seg_idx); + current_data_offset = Some(offset); + } + omf::record_type::LIDATA | omf::record_type::LIDATA32 => { + let (seg_idx, offset) = + self.parse_lidata(inner_data, record_type == omf::record_type::LIDATA32)?; + current_segment = Some(seg_idx); + current_data_offset = Some(offset); + } + 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", + )); + } + } + omf::record_type::MODEND | omf::record_type::MODEND32 => { + // End of module + break; + } + _ => { + // Skip unknown record types + } + } + + offset += record_length as u64 + 3; + } + + if offset == 0 { + return Err(Error("No OMF records found")); + } + + Ok(()) + } + + fn parse_header(&mut self, data: &'data [u8]) -> Result<()> { + if let Some((name, _)) = read_counted_string(data) { + self.module_name = core::str::from_utf8(name).ok(); + } + Ok(()) + } + + fn parse_names(&mut self, data: &'data [u8]) -> Result<()> { + let mut offset = 0; + while offset < data.len() { + if let Some((name, size)) = read_counted_string(&data[offset..]) { + self.names.push(name); + offset += size; + } else { + break; + } + } + Ok(()) + } + + fn parse_segdef(&mut self, data: &'data [u8], is_32bit: bool) -> Result<()> { + let mut offset = 0; + + // Parse ACBP byte + if offset >= data.len() { + return Err(Error("Truncated SEGDEF record")); + } + let acbp = data[offset]; + offset += 1; + + let alignment = match (acbp >> 5) & 0x07 { + 0 => omf::SegmentAlignment::Absolute, + 1 => omf::SegmentAlignment::Byte, + 2 => omf::SegmentAlignment::Word, + 3 => omf::SegmentAlignment::Paragraph, + 4 => omf::SegmentAlignment::Page, + 5 => omf::SegmentAlignment::DWord, + 6 => omf::SegmentAlignment::Page4K, + _ => return Err(Error("Invalid segment alignment")), + }; + + let combination = match (acbp >> 2) & 0x07 { + 0 => omf::SegmentCombination::Private, + 2 => omf::SegmentCombination::Public, + 5 => omf::SegmentCombination::Stack, + 6 => omf::SegmentCombination::Common, + _ => return Err(Error("Invalid segment combination")), + }; + + let use32 = (acbp & 0x01) != 0; + + // Skip frame number and offset for absolute segments + if alignment == omf::SegmentAlignment::Absolute { + offset += 3; // frame (2) + offset (1) + } + + // Parse segment length + let length = if is_32bit || use32 { + if offset + 4 > data.len() { + return Err(Error("Truncated SEGDEF record")); + } + let length = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + 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; + offset += 2; + length + }; + + // Parse segment name index + let (name_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid segment name index"))?; + offset += size; + + // Parse class name index + let (class_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid class name index"))?; + offset += size; + + // Parse overlay name index + let (overlay_index, _) = + read_index(&data[offset..]).ok_or(Error("Invalid overlay name index"))?; + + self.segments.push(OmfSegment { + name_index, + class_index, + overlay_index, + alignment, + combination, + use32, + length, + data_chunks: Vec::new(), + relocations: Vec::new(), + }); + + Ok(()) + } + + fn parse_grpdef(&mut self, data: &'data [u8]) -> Result<()> { + let mut offset = 0; + + // Parse group name index + let (name_index, size) = read_index(data).ok_or(Error("Invalid group name index"))?; + offset += size; + + let mut segments = Vec::new(); + + // Parse segment indices + while offset < data.len() { + if data[offset] == 0xFF { + // Segment index follows + offset += 1; + let (seg_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid segment index in group"))?; + offset += size; + segments.push(seg_index); + } else { + break; + } + } + + self.groups.push(OmfGroup { + name_index, + segments, + }); + + Ok(()) + } + + fn parse_pubdef( + &mut self, + data: &'data [u8], + is_32bit: bool, + class: OmfSymbolClass, + ) -> Result<()> { + let mut offset = 0; + + // Parse group index + let (group_index, size) = read_index(data).ok_or(Error("Invalid group index"))?; + offset += size; + + // Parse segment index + let (segment_index, size) = + read_index(&data[offset..]).ok_or(Error("Invalid segment index"))?; + offset += size; + + // Read frame number if segment index is 0 (for absolute symbols) + let frame_number = if segment_index == 0 { + if offset + 2 > data.len() { + return Err(Error("Invalid frame number in PUBDEF")); + } + let frame = u16::from_le_bytes([data[offset], data[offset + 1]]); + offset += 2; + frame + } else { + 0 + }; + + // Parse public definitions + while offset < data.len() { + // Parse name + let Some((name, size)) = read_counted_string(&data[offset..]) else { + break; + }; + offset += size; + + // Parse offset + let pub_offset = if is_32bit { + if offset + 4 > data.len() { + break; + } + 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() { + break; + } + let off = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; + offset += 2; + off + }; + + // Parse type index + let (type_index, size) = read_index(&data[offset..]) + .ok_or(Error("Invalid type index in PUBDEF/LPUBDEF record"))?; + offset += size; + + self.symbols.push(OmfSymbol { + symbol_index: self.symbols.len(), + name, + class, + group_index, + segment_index, + frame_number, + offset: pub_offset, + type_index, + kind: read::SymbolKind::Unknown, // Will be computed later + }); + } + + Ok(()) + } + + fn parse_extdef(&mut self, data: &'data [u8], class: OmfSymbolClass) -> Result<()> { + let mut offset = 0; + + while offset < data.len() { + // Parse name + let Some((name, size)) = read_counted_string(&data[offset..]) else { + break; + }; + offset += size; + + // Parse type index + let (type_index, size) = read_index(&data[offset..]) + .ok_or(Error("Invalid type index in EXTDEF/LEXTDEF/CEXTDEF 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)); + } + + Ok(()) + } + + fn parse_comdef(&mut self, data: &'data [u8], class: OmfSymbolClass) -> Result<()> { + let mut offset = 0; + + while offset < data.len() { + // Parse name + let Some((name, size)) = read_counted_string(&data[offset..]) else { + break; + }; + offset += size; + + // Parse type index + let (type_index, size) = read_index(&data[offset..]) + .ok_or(Error("Invalid type index in COMDEF/LCOMDEF record"))?; + offset += size; + + // Parse data type and communal length + if offset >= data.len() { + break; + } + let data_type = data[offset]; + offset += 1; + + let communal_length = match data_type { + 0x61 => { + // FAR data - number of elements followed by element size + let (num_elements, size1) = read_encoded_value(&data[offset..]) + .ok_or(Error("Invalid number of elements in FAR COMDEF"))?; + offset += size1; + let (element_size, size2) = read_encoded_value(&data[offset..]) + .ok_or(Error("Invalid element size in FAR COMDEF"))?; + offset += size2; + num_elements * element_size + } + 0x62 => { + // NEAR data - size in bytes + let (size_val, size_bytes) = read_encoded_value(&data[offset..]) + .ok_or(Error("Invalid size in NEAR COMDEF"))?; + offset += size_bytes; + size_val + } + _ => { + 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, + }); + + // Add to external_order for symbols that contribute to external-name table + self.external_order.push(read::SymbolIndex(sym_idx)); + } + + Ok(()) + } + + fn parse_comdat(&mut self, data: &'data [u8], is_32bit: bool) -> Result<()> { + let mut offset = 0; + + // Parse flags byte + if offset >= data.len() { + return Err(Error("Truncated COMDAT record")); + } + let _flags = data[offset]; + offset += 1; + + // Parse attributes byte + if offset >= data.len() { + return Err(Error("Truncated COMDAT record")); + } + 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 + 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 + }; + + // Parse data offset + let _data_offset = if is_32bit { + if offset + 4 > data.len() { + return Err(Error("Truncated COMDAT 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 COMDAT record")); + } + let off = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; + offset += 2; + off + }; + + // Parse type index + let (_type_index, size) = + 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) + 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..]) + .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 + } + } + } + + // Parse public name - this is an index into LNAMES + 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""); + + // Remaining data is the COMDAT content + let comdat_data = &data[offset..]; + + self.comdats.push(OmfComdatData { + name, + segment_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(()) + } + + fn parse_ledata(&mut self, data: &'data [u8], is_32bit: bool) -> Result<(usize, u32)> { + let mut offset = 0; + + // Parse segment index + 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")); + } + + // Parse data offset + let data_offset = if is_32bit { + if offset + 4 > data.len() { + return Err(Error("Truncated LEDATA 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 LEDATA record")); + } + let off = u16::from_le_bytes([data[offset], data[offset + 1]]) as u32; + offset += 2; + 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 + .data_chunks + .push((data_offset, OmfDataChunk::Direct(&data[offset..]))); + } + + Ok((seg_idx, data_offset)) + } + + fn parse_fixupp( + &mut self, + data: &'data [u8], + is_32bit: bool, + seg_idx: usize, + data_offset: u32, + frame_threads: &mut [Option; 4], + target_threads: &mut [Option; 4], + ) -> Result<()> { + let mut offset = 0; + + while offset < data.len() { + let b = data[offset]; + offset += 1; + + if (b & 0x80) == 0 { + // THREAD subrecord + let is_frame = (b & 0x40) != 0; // D-bit + let method = (b >> 2) & 0x07; // Method bits + let thread_num = (b & 0x03) as usize; // Thread number (0-3) + + let index = if method < 3 { + // Methods 0-2 have an index + let (idx, size) = read_index(&data[offset..]) + .ok_or(Error("Invalid index in THREAD subrecord"))?; + offset += size; + idx + } else if method == 3 { + // Method 3 has a raw frame number + if offset + 2 > data.len() { + return Err(Error("Invalid frame number in THREAD subrecord")); + } + let frame_num = u16::from_le_bytes([data[offset], data[offset + 1]]); + offset += 2; + frame_num + } else { + 0 + }; + + // Store the thread definition + let thread_def = ThreadDef { method, index }; + if is_frame { + frame_threads[thread_num] = Some(thread_def); + } else { + target_threads[thread_num] = Some(thread_def); + } + } else { + // FIXUP subrecord + if offset + 1 > data.len() { + return Err(Error("Truncated FIXUP location")); + } + let locat = data[offset] as u32 | (((b as u32) & 0x03) << 8); + offset += 1; + + let location = match (b >> 2) & 0x0F { + 0 => omf::FixupLocation::LowByte, + 1 => omf::FixupLocation::Offset, + 2 => omf::FixupLocation::Base, + 3 => omf::FixupLocation::Pointer, + 4 => omf::FixupLocation::HighByte, + 5 => omf::FixupLocation::LoaderOffset, + 9 => omf::FixupLocation::Offset32, + 11 => omf::FixupLocation::Pointer48, + 13 => omf::FixupLocation::LoaderOffset32, + _ => continue, // Skip unknown fixup types + }; + + // Parse fix data byte + if offset >= data.len() { + return Err(Error("Truncated FIXUP fix data")); + } + let fix_data = data[offset]; + offset += 1; + + // Check F-bit (bit 7 of fix_data) + let frame_via_thread = (fix_data & 0x80) != 0; + let (frame_method, frame_index) = if frame_via_thread { + // F=1: Use frame thread + 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")), + }; + (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 index = match method { + FrameMethod::SegmentIndex + | FrameMethod::GroupIndex + | FrameMethod::ExternalIndex => { + let (idx, size) = read_index(&data[offset..]) + .ok_or(Error("Truncated FIXUP frame datum: missing index data"))?; + offset += size; + idx + } + FrameMethod::FrameNumber => { + if offset + 2 > data.len() { + return Err(Error( + "Truncated FIXUP frame datum: missing frame number", + )); + } + let frame_num = u16::from_le_bytes([data[offset], data[offset + 1]]); + offset += 2; + frame_num + } + FrameMethod::Location | FrameMethod::Target => 0, + }; + (method, index) + }; + + // Check T-bit (bit 3 of fix_data) + let target_via_thread = (fix_data & 0x08) != 0; + let (target_method, target_index) = if target_via_thread { + // T=1: Use target thread + 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")), + }; + (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 index = match method { + TargetMethod::SegmentIndex + | TargetMethod::GroupIndex + | TargetMethod::ExternalIndex => { + let (idx, size) = read_index(&data[offset..]) + .ok_or(Error("Truncated FIXUP target datum: missing index data"))?; + offset += size; + idx + } + TargetMethod::FrameNumber => { + if offset + 2 > data.len() { + return Err(Error( + "Truncated FIXUP target datum: missing frame number", + )); + } + let frame_num = u16::from_le_bytes([data[offset], data[offset + 1]]); + offset += 2; + frame_num + } + }; + (method, index) + }; + + // Parse target displacement if present (P=0) + 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 { + return Err(Error("Truncated FIXUP 32-bit displacement")); + } + } else if offset + 2 <= data.len() { + 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 { + offset: data_offset + locat, + location, + frame_method, + target_method, + frame_index, + target_index, + target_displacement, + is_segment_relative, + }); + } + } + + 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 + 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 symbol by external-name index (1-based, as used in FIXUPP records) + pub fn external_symbol(&self, external_index: u16) -> Option<&OmfSymbol<'data>> { + let symbol_index = self + .external_order + .get(external_index.checked_sub(1)? as usize)?; + self.symbols.get(symbol_index.0) + } + + /// Get a name by index (1-based) + pub fn get_name(&self, index: u16) -> Option<&'data [u8]> { + let name_index = index.checked_sub(1)?; + self.names.get(name_index as usize).copied() + } + + /// Get all symbols (for iteration) + pub fn raw_symbols(&self) -> &[OmfSymbol<'data>] { + &self.symbols + } +} + +impl<'data, R: ReadRef<'data>> Object<'data> for OmfFile<'data, R> { + type Segment<'file> + = OmfSegmentRef<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type SegmentIterator<'file> + = OmfSegmentIterator<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type Section<'file> + = OmfSection<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type SectionIterator<'file> + = OmfSectionIterator<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type Comdat<'file> + = OmfComdat<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type ComdatIterator<'file> + = OmfComdatIterator<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type Symbol<'file> + = OmfSymbol<'data> + where + Self: 'file, + 'data: 'file; + type SymbolIterator<'file> + = OmfSymbolIterator<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type SymbolTable<'file> + = OmfSymbolTable<'data, 'file, R> + where + Self: 'file, + 'data: 'file; + type DynamicRelocationIterator<'file> + = NoDynamicRelocationIterator + where + Self: 'file, + 'data: 'file; + + fn architecture(&self) -> Architecture { + Architecture::I386 + } + + fn sub_architecture(&self) -> Option { + None + } + + fn is_little_endian(&self) -> bool { + true + } + + fn is_64(&self) -> bool { + false + } + + fn kind(&self) -> ObjectKind { + ObjectKind::Relocatable + } + + fn segments(&self) -> Self::SegmentIterator<'_> { + OmfSegmentIterator { + file: self, + index: 0, + } + } + + fn section_by_name_bytes<'file>( + &'file self, + section_name: &[u8], + ) -> Option> { + self.sections() + .find(|section| section.name_bytes() == Ok(section_name)) + } + + fn section_by_index(&self, index: SectionIndex) -> Result> { + let idx = index + .0 + .checked_sub(1) + .ok_or(Error("Invalid section index"))?; + if idx < self.segments.len() { + Ok(OmfSection { + file: self, + index: idx, + }) + } else { + Err(Error("Section index out of bounds")) + } + } + + fn sections(&self) -> Self::SectionIterator<'_> { + OmfSectionIterator { + file: self, + index: 0, + } + } + + fn comdats(&self) -> Self::ComdatIterator<'_> { + OmfComdatIterator { + file: self, + index: 0, + } + } + + 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()) + } + + fn symbols(&self) -> Self::SymbolIterator<'_> { + OmfSymbolIterator { + file: self, + index: 0, + } + } + + fn symbol_table(&self) -> Option> { + Some(OmfSymbolTable { file: self }) + } + + fn dynamic_symbols(&self) -> Self::SymbolIterator<'_> { + OmfSymbolIterator { + file: self, + index: usize::MAX, // Empty iterator + } + } + + fn dynamic_symbol_table(&self) -> Option> { + None + } + + fn dynamic_relocations(&self) -> Option> { + None + } + + 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()) + } + + 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()) + } + + fn has_debug_symbols(&self) -> bool { + false + } + + fn mach_uuid(&self) -> Result> { + Ok(None) + } + + fn build_id(&self) -> Result> { + Ok(None) + } + + fn gnu_debuglink(&self) -> Result> { + Ok(None) + } + + fn gnu_debugaltlink(&self) -> Result> { + Ok(None) + } + + fn pdb_info(&self) -> Result>> { + Ok(None) + } + + fn relative_address_base(&self) -> u64 { + 0 + } + + fn entry(&self) -> u64 { + 0 + } + + fn flags(&self) -> FileFlags { + FileFlags::None + } +} + +/// Thread definition for FIXUPP parsing +#[derive(Debug, Clone, Copy)] +struct ThreadDef { + /// 3-bit method (frame or target method) + method: u8, + /// Index value (meaning depends on method) + index: u16, +} + +/// Target method types for fixups +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub(super) enum TargetMethod { + /// Segment index + SegmentIndex = 0, + /// Group index + GroupIndex = 1, + /// External index + ExternalIndex = 2, + /// Frame number (absolute) + FrameNumber = 3, +} + +/// Frame method types for fixups +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub(super) enum FrameMethod { + /// Segment index + SegmentIndex = 0, + /// Group index + GroupIndex = 1, + /// External index + ExternalIndex = 2, + /// Frame number (absolute) + FrameNumber = 3, + /// Location (use fixup location) + Location = 4, + /// Target (use target's frame) + Target = 5, +} + +/// 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)?; + + debug_assert_eq!(write_offset, expanded_size); + debug_assert_eq!(consumed, orig_size); + + Ok(result) +} + +fn expand_lidata_block_into( + data: &[u8], + 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; + + if block_count == 0 { + if offset >= data.len() { + return Ok(offset); + } + + 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"))?; + if end > output.len() { + return Err(Error("LIDATA expanded size mismatch")); + } + output[*write_offset..end].copy_from_slice(block_data); + *write_offset = end; + } + } else { + let mut block_offset = offset; + 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() { + return Err(Error("Truncated LIDATA block")); + } + } + + 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"))?; + 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); + } + *write_offset = dest_end; + } + + offset = block_offset; + } + + Ok(offset) +} + +fn lidata_block_expanded_size(data: &[u8]) -> Result<(usize, usize)> { + 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 { + let consumed = lidata_block_size(data)?; + if consumed > 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)) + } else { + let mut block_offset = offset; + let mut single_iteration = 0usize; + + 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() { + return Err(Error("Truncated LIDATA block")); + } + single_iteration = single_iteration + .checked_add(expanded) + .ok_or(Error("LIDATA expanded size overflow"))?; + } + + 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) +} + +/// Helper to read an OMF index (1 or 2 bytes) +fn read_index(data: &[u8]) -> Option<(u16, usize)> { + if data.is_empty() { + return None; + } + + let first_byte = data[0]; + if first_byte & 0x80 == 0 { + // 1-byte index + Some((first_byte as u16, 1)) + } else if data.len() >= 2 { + // 2-byte index + let high = (first_byte & 0x7F) as u16; + let low = data[1] as u16; + Some((high << 8 | low, 2)) + } else { + None + } +} + +/// Helper to read a counted string (length byte followed by string) +fn read_counted_string(data: &[u8]) -> Option<(&[u8], usize)> { + if data.is_empty() { + return None; + } + + let length = data[0] as usize; + if data.len() > length { + Some((&data[1..1 + length], 1 + length)) + } else { + None + } +} + +/// 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 { + // 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 + } + } 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 + } + } 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 + } + } else { + // Unknown encoding + None + } +} diff --git a/src/read/omf/mod.rs b/src/read/omf/mod.rs new file mode 100644 index 00000000..756900fd --- /dev/null +++ b/src/read/omf/mod.rs @@ -0,0 +1,19 @@ +//! Support for reading OMF files. + +mod comdat; +pub use comdat::*; + +mod file; +pub use file::*; + +mod relocation; +pub use relocation::*; + +mod section; +pub use section::*; + +mod segment; +pub use segment::*; + +mod symbol; +pub use symbol::*; diff --git a/src/read/omf/relocation.rs b/src/read/omf/relocation.rs new file mode 100644 index 00000000..b616d7c7 --- /dev/null +++ b/src/read/omf/relocation.rs @@ -0,0 +1,154 @@ +use crate::read::ReadRef; +use crate::{ + omf, Relocation, RelocationEncoding, RelocationFlags, RelocationKind, RelocationTarget, + SectionIndex, SymbolIndex, +}; + +use super::{FrameMethod, OmfFile, TargetMethod}; + +/// An OMF fixup (relocation entry). +#[derive(Debug, Clone)] +pub(super) struct OmfFixup { + /// Offset in segment where fixup is applied + pub(super) offset: u32, + /// Location type (what to patch) + pub(super) location: omf::FixupLocation, + /// Frame method + pub(super) frame_method: FrameMethod, + /// Target method + pub(super) target_method: TargetMethod, + /// Frame index (meaning depends on frame_method) + pub(super) frame_index: u16, + /// Target index (meaning depends on target_method) + pub(super) target_index: u16, + /// Target displacement + pub(super) target_displacement: u32, + /// M-bit: true for segment-relative, false for PC-relative + pub(super) is_segment_relative: bool, +} + +/// An iterator over OMF relocations. +#[derive(Debug)] +pub struct OmfRelocationIterator<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) segment_index: usize, + pub(super) index: usize, +} + +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 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 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::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 + } + } + TargetMethod::GroupIndex | TargetMethod::FrameNumber => RelocationTarget::Absolute, + }; + + let fixup_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), + FrameMethod::FrameNumber => omf::FixupFrame::FrameNumber(reloc.frame_index), + FrameMethod::Location => omf::FixupFrame::Location, + FrameMethod::Target => omf::FixupFrame::Target, + }; + + let fixup_target = match reloc.target_method { + TargetMethod::SegmentIndex => omf::FixupTarget::Segment(reloc.target_index), + TargetMethod::GroupIndex => omf::FixupTarget::Group(reloc.target_index), + TargetMethod::ExternalIndex => omf::FixupTarget::External(reloc.target_index), + TargetMethod::FrameNumber => omf::FixupTarget::FrameNumber(reloc.target_index), + }; + + let relocation = Relocation { + kind, + encoding: RelocationEncoding::Generic, + size, + target, + addend: (reloc.target_displacement as i64) + base_addend, + implicit_addend: false, + flags: RelocationFlags::Omf { + location: reloc.location, + mode: if reloc.is_segment_relative { + omf::FixupMode::SegmentRelative + } else { + omf::FixupMode::SelfRelative + }, + frame: fixup_frame, + target: fixup_target, + // target_displacement: reloc.target_displacement, + }, + }; + + Some((reloc.offset as u64, relocation)) + } +} diff --git a/src/read/omf/section.rs b/src/read/omf/section.rs new file mode 100644 index 00000000..45dbd248 --- /dev/null +++ b/src/read/omf/section.rs @@ -0,0 +1,252 @@ +use alloc::borrow::Cow; +use alloc::{vec, vec::Vec}; +use core::str; + +use crate::read::{ + self, CompressedData, CompressedFileRange, Error, ObjectSection, ReadRef, RelocationMap, + Result, SectionFlags, SectionIndex, SectionKind, +}; + +use super::{expand_lidata_block, OmfDataChunk, OmfFile, OmfRelocationIterator, OmfSegment}; + +/// A section in an OMF file. +#[derive(Debug)] +pub struct OmfSection<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +/// 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 + pub(super) segments: Vec, +} + +impl<'data, 'file, R: ReadRef<'data>> OmfSection<'data, 'file, R> { + fn segment(&self) -> &OmfSegment<'data> { + &self.file.segments[self.index] + } +} + +impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for OmfSection<'data, 'file, R> {} + +impl<'data, 'file, R: ReadRef<'data>> ObjectSection<'data> for OmfSection<'data, 'file, R> { + type RelocationIterator = OmfRelocationIterator<'data, 'file, R>; + + fn index(&self) -> SectionIndex { + SectionIndex(self.index + 1) + } + + fn address(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + self.segment().length as u64 + } + + 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, + } + } + + fn file_range(&self) -> Option<(u64, u64)> { + 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", + )) + } + + fn data_range(&self, address: u64, size: u64) -> Result> { + let segment = self.segment(); + let offset = address as usize; + let end = offset + .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 + 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])); + } + } + } + + // Range spans multiple chunks, includes LIDATA, or is not available + Ok(None) + } + + fn compressed_file_range(&self) -> Result { + Ok(CompressedFileRange::none(self.file_range())) + } + + fn compressed_data(&self) -> Result> { + Ok(CompressedData::none(self.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")); + } + } + } + } + } + + Ok(Cow::Owned(result)) + } + + 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")) + } + + fn name(&self) -> Result<&'data str> { + str::from_utf8(self.name_bytes()?).map_err(|_| Error("Invalid UTF-8 in segment name")) + } + + fn segment_name_bytes(&self) -> Result> { + Ok(None) + } + + fn segment_name(&self) -> Result> { + Ok(None) + } + + fn kind(&self) -> SectionKind { + self.file.segment_section_kind(self.index) + } + + fn relocations(&self) -> Self::RelocationIterator { + OmfRelocationIterator { + file: self.file, + segment_index: self.index, + index: 0, + } + } + + fn relocation_map(&self) -> Result { + RelocationMap::new(self.file, self) + } + + 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 + } +} + +/// An iterator over OMF sections. +#[derive(Debug)] +pub struct OmfSectionIterator<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSectionIterator<'data, 'file, R> { + type Item = OmfSection<'data, 'file, R>; + + fn next(&mut self) -> Option { + if self.index < self.file.segments.len() { + let section = OmfSection { + file: self.file, + index: self.index, + }; + self.index += 1; + Some(section) + } else { + None + } + } +} diff --git a/src/read/omf/segment.rs b/src/read/omf/segment.rs new file mode 100644 index 00000000..1055f14b --- /dev/null +++ b/src/read/omf/segment.rs @@ -0,0 +1,153 @@ +use alloc::vec::Vec; + +use crate::read::{self, ObjectSegment, ReadRef, Result}; +use crate::{omf, SegmentFlags}; + +use super::{OmfFile, OmfFixup}; + +/// An OMF segment definition +#[derive(Debug, Clone)] +pub struct OmfSegment<'data> { + /// Segment name index (into 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 data chunks (offset, data) + /// Multiple LEDATA/LIDATA records can contribute to a single segment + pub(super) data_chunks: Vec<(u32, OmfDataChunk<'data>)>, + /// Relocations for this segment + pub(super) relocations: Vec, +} + +/// Data chunk for a segment +#[derive(Debug, Clone)] +pub(super) enum OmfDataChunk<'data> { + /// Direct data from LEDATA record + Direct(&'data [u8]), + /// Compressed/iterated data from LIDATA record (needs expansion) + Iterated(&'data [u8]), +} + +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); + } + _ => {} + } + } + } + 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(_))) + } +} + +/// An OMF segment reference. +#[derive(Debug)] +pub struct OmfSegmentRef<'data, 'file, R: ReadRef<'data>> { + file: &'file OmfFile<'data, R>, + index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for OmfSegmentRef<'data, 'file, R> {} + +impl<'data, 'file, R: ReadRef<'data>> ObjectSegment<'data> for OmfSegmentRef<'data, 'file, R> { + fn address(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + self.file.segments[self.index].length as u64 + } + + 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, + } + } + + fn file_range(&self) -> (u64, u64) { + (0, 0) + } + + fn data(&self) -> Result<&'data [u8]> { + // OMF segments don't have direct file mapping + Ok(&[]) + } + + fn data_range(&self, _address: u64, _size: u64) -> Result> { + Ok(None) + } + + fn name_bytes(&self) -> Result> { + Ok(self + .file + .get_name(self.file.segments[self.index].name_index)) + } + + 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), + } + } + + fn flags(&self) -> SegmentFlags { + SegmentFlags::None + } +} + +/// An iterator over OMF segments. +#[derive(Debug)] +pub struct OmfSegmentIterator<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSegmentIterator<'data, 'file, R> { + type Item = OmfSegmentRef<'data, 'file, R>; + + fn next(&mut self) -> Option { + if self.index < self.file.segments.len() { + let segment = OmfSegmentRef { + file: self.file, + index: self.index, + }; + self.index += 1; + Some(segment) + } else { + None + } + } +} diff --git a/src/read/omf/symbol.rs b/src/read/omf/symbol.rs new file mode 100644 index 00000000..b342cc4a --- /dev/null +++ b/src/read/omf/symbol.rs @@ -0,0 +1,195 @@ +use core::str; + +use crate::read::{ + self, Error, ObjectSymbol, ObjectSymbolTable, ReadRef, Result, SectionIndex, SymbolFlags, + SymbolIndex, SymbolKind, SymbolScope, SymbolSection, +}; + +use super::OmfFile; + +/// An OMF symbol +#[derive(Debug, Clone)] +pub struct OmfSymbol<'data> { + /// Symbol table index + pub symbol_index: usize, + /// Symbol name + pub 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, + /// Type index (usually 0) + pub type_index: u16, + /// Pre-computed symbol kind + pub kind: SymbolKind, +} + +/// Symbol class for OMF symbols +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OmfSymbolClass { + /// Public symbol (PUBDEF) + Public, + /// Local public symbol (LPUBDEF) + LocalPublic, + /// External symbol (EXTDEF) + External, + /// Local external symbol (LEXTDEF) + LocalExternal, + /// Communal symbol (COMDEF) + Communal, + /// Local communal symbol (LCOMDEF) + LocalCommunal, + /// COMDAT external symbol (CEXTDEF) + ComdatExternal, +} + +impl<'data> read::private::Sealed for OmfSymbol<'data> {} + +impl<'data> ObjectSymbol<'data> for OmfSymbol<'data> { + fn index(&self) -> SymbolIndex { + SymbolIndex(self.symbol_index) + } + + fn name_bytes(&self) -> Result<&'data [u8]> { + Ok(self.name) + } + + fn name(&self) -> Result<&'data str> { + core::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) + } else { + self.offset as u64 + } + } + + fn size(&self) -> u64 { + 0 // OMF doesn't store symbol sizes + } + + fn kind(&self) -> SymbolKind { + self.kind + } + + fn section(&self) -> SymbolSection { + if self.segment_index == 0 { + if self.frame_number != 0 { + SymbolSection::Absolute + } else { + SymbolSection::Undefined + } + } else { + SymbolSection::Section(SectionIndex(self.segment_index as usize)) + } + } + + fn is_undefined(&self) -> bool { + self.segment_index == 0 && self.frame_number == 0 + } + + fn is_definition(&self) -> bool { + self.segment_index != 0 || self.frame_number != 0 + } + + fn is_common(&self) -> bool { + matches!( + self.class, + super::OmfSymbolClass::Communal | super::OmfSymbolClass::LocalCommunal + ) + } + + fn is_weak(&self) -> bool { + false + } + + 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 + } + } + } + } + + fn is_global(&self) -> bool { + !self.is_local() + } + + fn is_local(&self) -> bool { + matches!( + self.class, + super::OmfSymbolClass::LocalPublic + | super::OmfSymbolClass::LocalExternal + | super::OmfSymbolClass::LocalCommunal + ) + } + + fn flags(&self) -> SymbolFlags { + SymbolFlags::None + } +} + +/// An iterator over OMF symbols. +#[derive(Debug)] +pub struct OmfSymbolIterator<'data, 'file, R: ReadRef<'data> = &'data [u8]> { + pub(super) file: &'file OmfFile<'data, R>, + pub(super) index: usize, +} + +impl<'data, 'file, R: ReadRef<'data>> Iterator for OmfSymbolIterator<'data, 'file, R> { + type Item = OmfSymbol<'data>; + + fn next(&mut self) -> Option { + let symbol = self.file.symbols.get(self.index)?.clone(); + self.index += 1; + Some(symbol) + } +} + +/// An OMF symbol table. +#[derive(Debug)] +pub struct OmfSymbolTable<'data, 'file, R: ReadRef<'data>> { + pub(super) file: &'file OmfFile<'data, R>, +} + +impl<'data, 'file, R: ReadRef<'data>> read::private::Sealed for OmfSymbolTable<'data, 'file, R> {} + +impl<'data, 'file, R: ReadRef<'data>> ObjectSymbolTable<'data> for OmfSymbolTable<'data, 'file, R> { + type Symbol = OmfSymbol<'data>; + type SymbolIterator = OmfSymbolIterator<'data, 'file, R>; + + fn symbols(&self) -> Self::SymbolIterator { + OmfSymbolIterator { + file: self.file, + index: 0, + } + } + + fn symbol_by_index(&self, index: SymbolIndex) -> Result { + self.file + .symbols + .get(index.0) + .cloned() + .ok_or(Error("Symbol index out of bounds")) + } +} diff --git a/testfiles b/testfiles index 5b121e59..e7ec8ce8 160000 --- a/testfiles +++ b/testfiles @@ -1 +1 @@ -Subproject commit 5b121e59e36d00567366691765c0fce3cb72b5e3 +Subproject commit e7ec8ce87c55569e8511e6a0f157fdcc2d641388 diff --git a/tests/read/mod.rs b/tests/read/mod.rs index 48e005ee..77931a43 100644 --- a/tests/read/mod.rs +++ b/tests/read/mod.rs @@ -3,3 +3,4 @@ mod coff; mod elf; mod macho; +mod omf; diff --git a/tests/read/omf.rs b/tests/read/omf.rs new file mode 100644 index 00000000..102ae182 --- /dev/null +++ b/tests/read/omf.rs @@ -0,0 +1,107 @@ +#[cfg(feature = "std")] +use object::{Object, ObjectSection, ObjectSymbol, RelocationKind}; + +#[cfg(feature = "std")] +#[test] +fn test_comprehensive() { + let path = "testfiles/omf/comprehensive_test.obj"; + let data = std::fs::read(path).unwrap_or_else(|_| panic!("Failed to read {}", path)); + + let file = object::File::parse(&data[..]).unwrap(); + assert_eq!(file.format(), object::BinaryFormat::Omf); + + // Check sections + let sections: Vec<_> = file.sections().collect(); + assert!( + sections.len() >= 3, + "Should have at least CODE, DATA, and BSS sections" + ); + + // Check for relocations (tests thread subrecord and F/T bit handling) + let mut total_relocations = 0; + for section in file.sections() { + let relocs: Vec<_> = section.relocations().collect(); + total_relocations += relocs.len(); + + // Check for both PC-relative and segment-relative relocations (M-bit test) + for (_offset, reloc) in &relocs { + let _kind = reloc.kind(); // Should have both Relative and Absolute + } + } + assert!( + total_relocations > 0, + "Should have relocations (tests thread/fixup parsing)" + ); + + // Check symbols (tests PUBDEF/EXTDEF parsing) + let symbols: Vec<_> = file.symbols().collect(); + assert!(!symbols.is_empty(), "Should have symbols"); + + // Check for COMDEF symbols if supported + let has_comdef = symbols + .iter() + .any(|sym| sym.name().unwrap_or("").contains("shared")); + assert!(has_comdef, "Should have COMDEF symbols (shared variables)"); +} + +#[cfg(feature = "std")] +#[test] +fn test_lidata() { + let path = "testfiles/omf/test_lidata.obj"; + let data = std::fs::read(path).unwrap_or_else(|_| panic!("Failed to read {}", path)); + + let file = object::File::parse(&data[..]).unwrap(); + assert_eq!(file.format(), object::BinaryFormat::Omf); + + // Check that sections have data (LIDATA should be expanded) + let mut total_data_size = 0; + for section in file.sections() { + // Use uncompressed_data to get expanded LIDATA + if let Ok(data) = section.uncompressed_data() { + total_data_size += data.len(); + } + } + assert_eq!(total_data_size, 401); +} + +#[cfg(feature = "std")] +#[test] +fn test_relocations() { + let path = "testfiles/omf/comprehensive_test.obj"; + let data = std::fs::read(path).unwrap_or_else(|_| panic!("Failed to read {}", path)); + let file = object::File::parse(&data[..]).unwrap(); + + let mut has_relative = false; + let mut has_absolute = false; + + for section in file.sections() { + for (_offset, reloc) in section.relocations() { + match reloc.kind() { + RelocationKind::Relative => has_relative = true, + RelocationKind::Absolute => has_absolute = true, + _ => {} + } + } + } + assert!(has_relative, "Should have Relative relocations (M=0)"); + assert!(has_absolute, "Should have Absolute relocations (M=1)"); +} + +#[cfg(feature = "std")] +#[test] +fn test_comdat() { + let path = "testfiles/omf/test_comdat.obj"; + let data = std::fs::read(path).unwrap_or_else(|_| panic!("Failed to read {}", path)); + + let file = object::File::parse(&data[..]).unwrap(); + assert_eq!(file.format(), object::BinaryFormat::Omf); + + // COMDAT support would show up as sections or symbols + let sections: Vec<_> = file.sections().collect(); + let symbols: Vec<_> = file.symbols().collect(); + + assert!( + !sections.is_empty() || !symbols.is_empty(), + "Should have parsed some content from COMDAT file" + ); +}