diff --git a/crates/examples/src/objdump.rs b/crates/examples/src/objdump.rs index 257560aa..cfb6d421 100644 --- a/crates/examples/src/objdump.rs +++ b/crates/examples/src/objdump.rs @@ -1,6 +1,7 @@ use object::read::archive::ArchiveFile; +use object::read::coff; use object::read::macho::{DyldCache, FatArch, FatHeader}; -use object::{Endianness, Object, ObjectComdat, ObjectSection, ObjectSymbol}; +use object::{Endianness, FileKind, Object, ObjectComdat, ObjectSection, ObjectSymbol}; use std::io::{Result, Write}; pub fn print( @@ -21,7 +22,11 @@ pub fn print( writeln!(w)?; writeln!(w, "{}:", String::from_utf8_lossy(member.name()))?; if let Ok(data) = member.data(file) { - dump_object(w, e, data)?; + if FileKind::parse(data) == Ok(FileKind::CoffImportFile) { + dump_import(w, e, data)?; + } else { + dump_object(w, e, data)?; + } } } } @@ -245,3 +250,25 @@ fn dump_parsed_object(w: &mut W, e: &mut E, file: &object::F Ok(()) } + +fn dump_import(w: &mut W, e: &mut E, data: &[u8]) -> Result<()> { + let file = match coff::CoffImportFile::parse(data) { + Ok(import) => import, + Err(err) => { + writeln!(e, "Failed to parse short import: {}", err)?; + return Ok(()); + } + }; + + writeln!(w, "Format: Short Import File")?; + writeln!(w, "Architecture: {:?}", file.architecture())?; + writeln!(w, "DLL: {:?}", String::from_utf8_lossy(file.dll()))?; + writeln!(w, "Symbol: {:?}", String::from_utf8_lossy(file.symbol()))?; + write!(w, "Import: ")?; + match file.import() { + coff::ImportName::Ordinal(n) => writeln!(w, "Ordinal({})", n)?, + coff::ImportName::Name(name) => writeln!(w, "Name({:?})", String::from_utf8_lossy(name))?, + } + writeln!(w, "Type: {:?}", file.import_type())?; + Ok(()) +} diff --git a/crates/examples/testfiles/coff/import_msvc.lib.objdump b/crates/examples/testfiles/coff/import_msvc.lib.objdump new file mode 100644 index 00000000..2d62f547 --- /dev/null +++ b/crates/examples/testfiles/coff/import_msvc.lib.objdump @@ -0,0 +1,256 @@ +Format: Archive (kind: Coff) + +test_x64.dll: +Format: Short Import File +Architecture: X86_64 +DLL: "test_x64.dll" +Symbol: "foo_x64" +Import: Name("foo_x64") +Type: Code + +test_x64.dll: +Format: Short Import File +Architecture: X86_64 +DLL: "test_x64.dll" +Symbol: "bar_x64" +Import: Ordinal(1) +Type: Code + +test_x64.dll: +Format: Short Import File +Architecture: X86_64 +DLL: "test_x64.dll" +Symbol: "FOO_x64" +Import: Name("FOO_x64") +Type: Data + +test_x64.dll: +Format: Short Import File +Architecture: X86_64 +DLL: "test_x64.dll" +Symbol: "BAR_x64" +Import: Name("BAR_x64") +Type: Const + +test_x64.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: X86_64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$5", address: 0, size: 0 } +Segment { name: ".idata$4", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$5", address: 0, size: 8, align: 8, kind: Data, flags: Coff { characteristics: c0400040 } } +3: Section { name: ".idata$4", address: 0, size: 8, align: 8, kind: Data, flags: Coff { characteristics: c0400040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "\u{7f}test_x64_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_x64.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: X86_64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$3", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$3", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_x64.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: X86_64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$2", address: 0, size: 0 } +Segment { name: ".idata$6", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$2", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } +3: Section { name: ".idata$6", address: 0, size: e, align: 2, kind: Data, flags: Coff { characteristics: c0200040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__IMPORT_DESCRIPTOR_test_x64", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } +2: Symbol { name: ".idata$2", address: 0, size: 0, kind: Section, section: Section(SectionIndex(2)), scope: Compilation, weak: false, flags: None } +3: Symbol { name: ".idata$6", address: 0, size: 0, kind: Data, section: Section(SectionIndex(3)), scope: Compilation, weak: false, flags: None } +4: Symbol { name: ".idata$4", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +5: Symbol { name: ".idata$5", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +6: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } +7: Symbol { name: "\u{7f}test_x64_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } + +.idata$2 relocations +(c, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(3)), addend: 0, implicit_addend: true }) +(0, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(4)), addend: 0, implicit_addend: true }) +(10, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(5)), addend: 0, implicit_addend: true }) + +Dynamic symbols + +test_x86.dll: +Format: Short Import File +Architecture: I386 +DLL: "test_x86.dll" +Symbol: "_foo_x86" +Import: Name("foo_x86") +Type: Code + +test_x86.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: I386 +Flags: Coff { characteristics: 100 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$5", address: 0, size: 0 } +Segment { name: ".idata$4", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$5", address: 0, size: 4, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } +3: Section { name: ".idata$4", address: 0, size: 4, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "\u{7f}test_x86_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_x86.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: I386 +Flags: Coff { characteristics: 100 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$3", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$3", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_x86.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: I386 +Flags: Coff { characteristics: 100 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$2", address: 0, size: 0 } +Segment { name: ".idata$6", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 42, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$2", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } +3: Section { name: ".idata$6", address: 0, size: e, align: 2, kind: Data, flags: Coff { characteristics: c0200040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__IMPORT_DESCRIPTOR_test_x86", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } +2: Symbol { name: ".idata$2", address: 0, size: 0, kind: Section, section: Section(SectionIndex(2)), scope: Compilation, weak: false, flags: None } +3: Symbol { name: ".idata$6", address: 0, size: 0, kind: Data, section: Section(SectionIndex(3)), scope: Compilation, weak: false, flags: None } +4: Symbol { name: ".idata$4", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +5: Symbol { name: ".idata$5", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +6: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } +7: Symbol { name: "\u{7f}test_x86_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } + +.idata$2 relocations +(c, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(3)), addend: 0, implicit_addend: true }) +(0, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(4)), addend: 0, implicit_addend: true }) +(10, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(5)), addend: 0, implicit_addend: true }) + +Dynamic symbols + +test_arm64ec.dll: +Format: Short Import File +Architecture: Unknown +DLL: "test_arm64ec.dll" +Symbol: "#foo_arm64ec" +Import: Name("foo_arm64ec") +Type: Code + +test_arm64ec.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: Aarch64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$5", address: 0, size: 0 } +Segment { name: ".idata$4", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 46, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$5", address: 0, size: 8, align: 8, kind: Data, flags: Coff { characteristics: c0400040 } } +3: Section { name: ".idata$4", address: 0, size: 8, align: 8, kind: Data, flags: Coff { characteristics: c0400040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "\u{7f}test_arm64ec_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_arm64ec.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: Aarch64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$3", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 46, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$3", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } + +Dynamic symbols + +test_arm64ec.dll: +Format: Coff Little-endian 32-bit +Kind: Relocatable +Architecture: Aarch64 +Flags: Coff { characteristics: 0 } +Relative Address Base: 0 +Entry Address: 0 +Segment { name: ".debug$S", address: 0, size: 0 } +Segment { name: ".idata$2", address: 0, size: 0 } +Segment { name: ".idata$6", address: 0, size: 0 } +1: Section { name: ".debug$S", address: 0, size: 46, align: 1, kind: Other, flags: Coff { characteristics: 42100040 } } +2: Section { name: ".idata$2", address: 0, size: 14, align: 4, kind: Data, flags: Coff { characteristics: c0300040 } } +3: Section { name: ".idata$6", address: 0, size: 12, align: 2, kind: Data, flags: Coff { characteristics: c0200040 } } + +Symbols +0: Symbol { name: "@comp.id", address: 0, size: 0, kind: Data, section: Absolute, scope: Compilation, weak: false, flags: None } +1: Symbol { name: "__IMPORT_DESCRIPTOR_test_arm64ec", address: 0, size: 0, kind: Data, section: Section(SectionIndex(2)), scope: Linkage, weak: false, flags: None } +2: Symbol { name: ".idata$2", address: 0, size: 0, kind: Section, section: Section(SectionIndex(2)), scope: Compilation, weak: false, flags: None } +3: Symbol { name: ".idata$6", address: 0, size: 0, kind: Data, section: Section(SectionIndex(3)), scope: Compilation, weak: false, flags: None } +4: Symbol { name: ".idata$4", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +5: Symbol { name: ".idata$5", address: 0, size: 0, kind: Section, section: Common, scope: Compilation, weak: false, flags: None } +6: Symbol { name: "__NULL_IMPORT_DESCRIPTOR", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } +7: Symbol { name: "\u{7f}test_arm64ec_NULL_THUNK_DATA", address: 0, size: 0, kind: Data, section: Undefined, scope: Linkage, weak: false, flags: None } + +.idata$2 relocations +(c, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(3)), addend: 0, implicit_addend: true }) +(0, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(4)), addend: 0, implicit_addend: true }) +(10, Relocation { kind: ImageOffset, encoding: Generic, size: 20, target: Symbol(SymbolIndex(5)), addend: 0, implicit_addend: true }) + +Dynamic symbols diff --git a/src/read/coff/import.rs b/src/read/coff/import.rs new file mode 100644 index 00000000..a88f38da --- /dev/null +++ b/src/read/coff/import.rs @@ -0,0 +1,145 @@ +//! Support for reading short import files. +//! +//! These are used by some Windows linkers as a more compact way to describe +//! dynamically imported symbols. + +use crate::read::{Architecture, Error, ReadError, ReadRef, Result}; +use crate::{pe, ByteString, Bytes, LittleEndian as LE}; + +/// A Windows short form description of a symbol to import. +/// Used in Windows import libraries. This is not an object file. +#[derive(Debug, Clone)] +pub struct CoffImportFile<'data> { + header: &'data pe::ImportObjectHeader, + dll: ByteString<'data>, + symbol: ByteString<'data>, + kind: ImportType, + import: Option>, +} +impl<'data> CoffImportFile<'data> { + /// Parse it. + pub fn parse>(data: R) -> Result { + let mut offset = 0; + let header = pe::ImportObjectHeader::parse(data, &mut offset)?; + let data_size = header.size_of_data.get(LE); + let mut strings = Bytes( + data.read_bytes(&mut offset, data_size as u64) + .read_error("Invalid COFF import library data size")?, + ); + let symbol = strings + .read_string() + .read_error("Could not read COFF import library symbol name")?; + let dll = strings + .read_string() + .read_error("Could not read COFF import library DLL name")?; + + // Unmangles a name by removing a `?`, `@` or `_` prefix. + fn strip_prefix(s: &[u8]) -> &[u8] { + match s.split_first() { + Some((b, rest)) if [b'?', b'@', b'_'].contains(b) => rest, + _ => s, + } + } + Ok(Self { + header, + dll: ByteString(dll), + symbol: ByteString(symbol), + kind: match header.name_type.get(LE) & 0b11 { + pe::IMPORT_OBJECT_CODE => ImportType::Code, + pe::IMPORT_OBJECT_DATA => ImportType::Data, + pe::IMPORT_OBJECT_CONST => ImportType::Const, + 0b11 => return Err(Error("Invalid COFF import library import type")), + _ => unreachable!("COFF import library ImportType must be a two bit number"), + }, + import: match (header.name_type.get(LE) >> 2) & 0b111 { + pe::IMPORT_OBJECT_ORDINAL => None, + pe::IMPORT_OBJECT_NAME => Some(symbol), + pe::IMPORT_OBJECT_NAME_NO_PREFIX => Some(strip_prefix(symbol)), + pe::IMPORT_OBJECT_NAME_UNDECORATE => { + Some(strip_prefix(symbol).split(|&b| b == b'@').next().unwrap()) + } + pe::IMPORT_OBJECT_NAME_EXPORTAS => Some( + strings + .read_string() + .read_error("Could not read COFF import library export name")?, + ), + 5..=7 => return Err(Error("Unknown COFF import library name type")), + _ => unreachable!("COFF import library name type must be a three bit number"), + } + .map(ByteString), + }) + } + + /// Get the machine type. + pub fn architecture(&self) -> Architecture { + match self.header.machine.get(LE) { + pe::IMAGE_FILE_MACHINE_ARMNT => Architecture::Arm, + pe::IMAGE_FILE_MACHINE_ARM64 => Architecture::Aarch64, + pe::IMAGE_FILE_MACHINE_I386 => Architecture::I386, + pe::IMAGE_FILE_MACHINE_AMD64 => Architecture::X86_64, + _ => Architecture::Unknown, + } + } + + /// The name of the DLL to import the symbol from. + pub fn dll(&self) -> &'data [u8] { + self.dll.0 + } + + /// The name exported from the DLL. + pub fn import(&self) -> ImportName<'data> { + match self.import { + Some(name) => ImportName::Name(name.0), + None => ImportName::Ordinal(self.header.ordinal_or_hint.get(LE)), + } + } + + /// The type of import. Usually either a function or data. + pub fn import_type(&self) -> ImportType { + self.kind + } + + /// The public symbol name + pub fn symbol(&self) -> &'data [u8] { + self.symbol.0 + } +} + +/// The name or ordinal to import. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ImportName<'data> { + /// Import by ordinal. Ordinarily this is a 1-based index. + Ordinal(u16), + /// Import by name. + Name(&'data [u8]), +} + +/// The kind of import. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ImportType { + /// Executable code + Code, + /// Some data + Data, + /// A constant value. + Const, +} + +impl pe::ImportObjectHeader { + /// Read the short import header. + /// + /// Also checks that the signature and version are valid. + /// Directly following this header will be the string data. + pub fn parse<'data, R: ReadRef<'data>>(data: R, offset: &mut u64) -> Result<&'data Self> { + let header = data + .read::(offset) + .read_error("Invalid COFF import library header size")?; + if header.sig1.get(LE) != 0 || header.sig2.get(LE) != pe::IMPORT_OBJECT_HDR_SIG2 { + Err(Error("Invalid COFF import library header")) + } else if header.version.get(LE) != 0 { + Err(Error("Unknown COFF import library header version")) + } else { + Ok(header) + } + } +} diff --git a/src/read/coff/mod.rs b/src/read/coff/mod.rs index d5b3caf3..26020d79 100644 --- a/src/read/coff/mod.rs +++ b/src/read/coff/mod.rs @@ -16,3 +16,6 @@ pub use relocation::*; mod comdat; pub use comdat::*; + +mod import; +pub use import::*; diff --git a/src/read/mod.rs b/src/read/mod.rs index 0a450359..092536d1 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -156,6 +156,9 @@ pub enum FileKind { /// This supports a larger number of sections. #[cfg(feature = "coff")] CoffBig, + /// A Windows short import file. + #[cfg(feature = "coff")] + CoffImportFile, /// A dyld cache file containing Mach-O images. #[cfg(feature = "macho")] DyldCache, @@ -263,6 +266,8 @@ impl FileKind { [0x01, 0xDF, ..] => FileKind::Xcoff32, #[cfg(feature = "xcoff")] [0x01, 0xF7, ..] => FileKind::Xcoff64, + #[cfg(feature = "coff")] + [0, 0, 0xFF, 0xFF, ..] => FileKind::CoffImportFile, _ => return Err(Error("Unknown file magic")), }; Ok(kind) diff --git a/testfiles b/testfiles index 0bd0f083..86bf162a 160000 --- a/testfiles +++ b/testfiles @@ -1 +1 @@ -Subproject commit 0bd0f08374ad21385687ce244c218f53733de77c +Subproject commit 86bf162a75f9234313ae58ea30ca61901d0cce58