Skip to content

Commit

Permalink
Read short import files
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisDenton committed Jun 16, 2023
1 parent 60c9721 commit 4a8ae07
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 2 deletions.
30 changes: 28 additions & 2 deletions crates/examples/src/objdump.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use object::import::{ImportName, ShortImportFile};
use object::read::archive::ArchiveFile;
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<W: Write, E: Write>(
Expand All @@ -21,7 +22,11 @@ pub fn print<W: Write, E: Write>(
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::ShortImport) {
dump_import(w, e, data)?;
} else {
dump_object(w, e, data)?;
}
}
}
}
Expand Down Expand Up @@ -245,3 +250,24 @@ fn dump_parsed_object<W: Write, E: Write>(w: &mut W, e: &mut E, file: &object::F

Ok(())
}

fn dump_import<W: Write, E: Write>(w: &mut W, e: &mut E, data: &[u8]) -> Result<()> {
let file = match ShortImportFile::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() {
ImportName::Ordinal(n) => writeln!(w, "Ordinal({n})")?,
ImportName::Name(name) => writeln!(w, "Name({:?})", String::from_utf8_lossy(name))?,
}
Ok(())
}
207 changes: 207 additions & 0 deletions src/read/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//! 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};

/// Short form description of a symbol to import.
/// Used in Windows import libraries.
#[derive(Debug, Clone)]
pub struct ShortImportFile<'data> {
header: &'data pe::ImportObjectHeader,
dll: ByteString<'data>,
symbol: ByteString<'data>,
kind: ImportType,
import: Option<ByteString<'data>>,
}
impl<'data> ShortImportFile<'data> {
/// Parse it.
pub fn parse<R: ReadRef<'data>>(data: R) -> Result<Self> {
let mut offset = 0;
let header = pe::ImportObjectHeader::parse(data, &mut offset)?;

let mut strings = Bytes(
data.read_bytes(&mut offset, header.data_size() as u64)
.read_error("Not enough data")?,
);
let symbol = strings
.read_string()
.read_error("Could not read symbol name")?;
let dll = strings
.read_string()
.read_error("Could not read DLL name")?;

// Unmangles a name by removing a `?`, `@` or `_` prefix.
fn strip_prefix(s: &[u8]) -> &[u8] {
match s.split_first() {
Some((b'?' | b'@' | b'_', rest)) => rest,
_ => s,
}
}
Ok(Self {
header,
dll: ByteString(dll),
symbol: ByteString(symbol),
kind: header.import_type()?,
import: match header.name_type()? {
ImportNameType::Ordinal => None,
ImportNameType::Name => Some(symbol),
ImportNameType::NameNoPrefix => Some(strip_prefix(symbol)),
ImportNameType::NameUndecorate => {
Some(strip_prefix(symbol).split(|&b| b == b'@').next().unwrap())
}
ImportNameType::NameExportAs => Some(
strings
.read_string()
.read_error("Could not read export name")?,
),
}
.map(ByteString),
})
}

/// Get the machine type.
pub fn architecture(&self) -> Architecture {
self.header.architecture()
}

/// 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()),
}
}

/// 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
Const,
}
impl ImportType {
/// `n` must be a two bit number.
fn from_u2(n: u16) -> Result<Self> {
match n {
pe::IMPORT_OBJECT_CODE => Ok(Self::Code),
pe::IMPORT_OBJECT_DATA => Ok(Self::Data),
pe::IMPORT_OBJECT_CONST => Ok(Self::Const),
0b11 => Err(Error("Invalid import type")),
_ => unreachable!("ImportType must be a two bit number"),
}
}
}

/// Determines how the symbol is imported.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImportNameType {
/// Import by an offset into the DLL's export table.
Ordinal,
/// Import using the same name as the symbol name.
Name,
/// Strip any leading `?`, `@` or `_` from the symbol name to get the import name.
NameNoPrefix,
/// The same as [`NameNoPrefix`] but also truncates at the next `@`.
NameUndecorate,
/// The import name is given after the DLL name.
NameExportAs,
}
impl ImportNameType {
/// `n` must be a three bit number.
fn from_u3(n: u16) -> Result<Self> {
match n {
pe::IMPORT_OBJECT_ORDINAL => Ok(ImportNameType::Ordinal),
pe::IMPORT_OBJECT_NAME => Ok(ImportNameType::Name),
pe::IMPORT_OBJECT_NAME_NO_PREFIX => Ok(ImportNameType::NameNoPrefix),
pe::IMPORT_OBJECT_NAME_UNDECORATE => Ok(ImportNameType::NameUndecorate),
pe::IMPORT_OBJECT_NAME_EXPORTAS => Ok(ImportNameType::NameExportAs),
5..=7 => Err(Error("Unknown name type")),
_ => unreachable!("ImportNameType must be a three bit number"),
}
}
}

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::<crate::pe::ImportObjectHeader>(offset)
.read_error("Invalid import header size")?;
if header.sig1.get(LE) != 0 || header.sig2.get(LE) != pe::IMPORT_OBJECT_HDR_SIG2 {
Err(Error("Invalid import header"))
} else if header.version.get(LE) != 0 {
Err(Error("Unknown import header version"))
} else {
Ok(header)
}
}

/// Get the machine type.
pub fn architecture(&self) -> Architecture {
match self.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 type of import can be code, data or const.
pub fn import_type(&self) -> Result<ImportType> {
ImportType::from_u2(self.name_type.get(LE) & 0b11)
}

/// Describes how to derive the import name.
///
/// Returns an error if the type is not recognised.
pub fn name_type(&self) -> Result<ImportNameType> {
ImportNameType::from_u3((self.name_type.get(LE) >> 2) & 0b111)
}

/// The index into the DLL's export table where the symbol may be found.
///
/// If `name_type` is not `Ordinal` then this is only a hint and may be ignored.
pub fn ordinal(&self) -> u16 {
self.ordinal_or_hint.get(LE)
}

/// The combined size of the null-terminated strings that follow the header.
pub fn data_size(&self) -> u32 {
self.size_of_data.get(LE)
}
}
8 changes: 8 additions & 0 deletions src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pub mod wasm;
#[cfg(feature = "xcoff")]
pub mod xcoff;

#[cfg(feature = "archive")]
pub mod import;

mod traits;
pub use traits::*;

Expand Down Expand Up @@ -165,6 +168,9 @@ pub enum FileKind {
/// A 64-bit ELF file.
#[cfg(feature = "elf")]
Elf64,
/// A Windows short import file.
#[cfg(feature = "archive")]
ShortImport,
/// A 32-bit Mach-O file.
#[cfg(feature = "macho")]
MachO32,
Expand Down Expand Up @@ -263,6 +269,8 @@ impl FileKind {
[0x01, 0xDF, ..] => FileKind::Xcoff32,
#[cfg(feature = "xcoff")]
[0x01, 0xF7, ..] => FileKind::Xcoff64,
#[cfg(feature = "archive")]
[0, 0, 0xFF, 0xFF, ..] => FileKind::ShortImport,
_ => return Err(Error("Unknown file magic")),
};
Ok(kind)
Expand Down

0 comments on commit 4a8ae07

Please sign in to comment.