object/read/coff/
import.rs

1//! Support for reading short import files.
2//!
3//! These are used by some Windows linkers as a more compact way to describe
4//! dynamically imported symbols.
5
6use crate::endian::LittleEndian as LE;
7use crate::pe;
8use crate::read::{
9    Architecture, ByteString, Bytes, Error, ReadError, ReadRef, Result, SubArchitecture,
10};
11
12/// A Windows short form description of a symbol to import.
13///
14/// Used in Windows import libraries to provide a mapping from
15/// a symbol name to a DLL export. This is not an object file.
16///
17/// This is a file that starts with [`pe::ImportObjectHeader`], and corresponds
18/// to [`crate::FileKind::CoffImport`].
19#[derive(Debug, Clone)]
20pub struct ImportFile<'data> {
21    header: &'data pe::ImportObjectHeader,
22    kind: ImportType,
23    dll: ByteString<'data>,
24    symbol: ByteString<'data>,
25    import: Option<ByteString<'data>>,
26}
27
28impl<'data> ImportFile<'data> {
29    /// Parse it.
30    pub fn parse<R: ReadRef<'data>>(data: R) -> Result<Self> {
31        let mut offset = 0;
32        let header = pe::ImportObjectHeader::parse(data, &mut offset)?;
33        let data = header.parse_data(data, &mut offset)?;
34
35        // Unmangles a name by removing a `?`, `@` or `_` prefix.
36        fn strip_prefix(s: &[u8]) -> &[u8] {
37            match s.split_first() {
38                Some((b, rest)) if [b'?', b'@', b'_'].contains(b) => rest,
39                _ => s,
40            }
41        }
42        Ok(Self {
43            header,
44            dll: data.dll,
45            symbol: data.symbol,
46            kind: match header.import_type() {
47                pe::IMPORT_OBJECT_CODE => ImportType::Code,
48                pe::IMPORT_OBJECT_DATA => ImportType::Data,
49                pe::IMPORT_OBJECT_CONST => ImportType::Const,
50                _ => return Err(Error("Invalid COFF import library import type")),
51            },
52            import: match header.name_type() {
53                pe::IMPORT_OBJECT_ORDINAL => None,
54                pe::IMPORT_OBJECT_NAME => Some(data.symbol()),
55                pe::IMPORT_OBJECT_NAME_NO_PREFIX => Some(strip_prefix(data.symbol())),
56                pe::IMPORT_OBJECT_NAME_UNDECORATE => Some(
57                    strip_prefix(data.symbol())
58                        .split(|&b| b == b'@')
59                        .next()
60                        .unwrap(),
61                ),
62                pe::IMPORT_OBJECT_NAME_EXPORTAS => data.export(),
63                _ => return Err(Error("Unknown COFF import library name type")),
64            }
65            .map(ByteString),
66        })
67    }
68
69    /// Get the machine type.
70    pub fn architecture(&self) -> Architecture {
71        match self.header.machine.get(LE) {
72            pe::IMAGE_FILE_MACHINE_ARMNT => Architecture::Arm,
73            pe::IMAGE_FILE_MACHINE_ARM64 | pe::IMAGE_FILE_MACHINE_ARM64EC => Architecture::Aarch64,
74            pe::IMAGE_FILE_MACHINE_I386 => Architecture::I386,
75            pe::IMAGE_FILE_MACHINE_AMD64 => Architecture::X86_64,
76            _ => Architecture::Unknown,
77        }
78    }
79
80    /// Get the sub machine type, if available.
81    pub fn sub_architecture(&self) -> Option<SubArchitecture> {
82        match self.header.machine.get(LE) {
83            pe::IMAGE_FILE_MACHINE_ARM64EC => Some(SubArchitecture::Arm64EC),
84            _ => None,
85        }
86    }
87
88    /// The public symbol name.
89    pub fn symbol(&self) -> &'data [u8] {
90        self.symbol.0
91    }
92
93    /// The name of the DLL to import the symbol from.
94    pub fn dll(&self) -> &'data [u8] {
95        self.dll.0
96    }
97
98    /// The name exported from the DLL.
99    pub fn import(&self) -> ImportName<'data> {
100        match self.import {
101            Some(name) => ImportName::Name(name.0),
102            None => ImportName::Ordinal(self.header.ordinal_or_hint.get(LE)),
103        }
104    }
105
106    /// The type of import. Usually either a function or data.
107    pub fn import_type(&self) -> ImportType {
108        self.kind
109    }
110}
111
112/// The name or ordinal to import from a DLL.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum ImportName<'data> {
115    /// Import by ordinal. Ordinarily this is a 1-based index.
116    Ordinal(u16),
117    /// Import by name.
118    Name(&'data [u8]),
119}
120
121/// The kind of import symbol.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum ImportType {
124    /// An executable code symbol.
125    Code,
126    /// A data symbol.
127    Data,
128    /// A constant value.
129    Const,
130}
131
132impl pe::ImportObjectHeader {
133    /// Read the short import header.
134    ///
135    /// Also checks that the signature and version are valid.
136    /// Directly following this header will be the string data.
137    pub fn parse<'data, R: ReadRef<'data>>(data: R, offset: &mut u64) -> Result<&'data Self> {
138        let header = data
139            .read::<pe::ImportObjectHeader>(offset)
140            .read_error("Invalid COFF import library header size")?;
141        if header.sig1.get(LE) != 0 || header.sig2.get(LE) != pe::IMPORT_OBJECT_HDR_SIG2 {
142            Err(Error("Invalid COFF import library header"))
143        } else if header.version.get(LE) != 0 {
144            Err(Error("Unknown COFF import library header version"))
145        } else {
146            Ok(header)
147        }
148    }
149
150    /// Parse the data following the header.
151    pub fn parse_data<'data, R: ReadRef<'data>>(
152        &self,
153        data: R,
154        offset: &mut u64,
155    ) -> Result<ImportObjectData<'data>> {
156        let mut data = Bytes(
157            data.read_bytes(offset, u64::from(self.size_of_data.get(LE)))
158                .read_error("Invalid COFF import library data size")?,
159        );
160        let symbol = data
161            .read_string()
162            .map(ByteString)
163            .read_error("Could not read COFF import library symbol name")?;
164        let dll = data
165            .read_string()
166            .map(ByteString)
167            .read_error("Could not read COFF import library DLL name")?;
168        let export = if self.name_type() == pe::IMPORT_OBJECT_NAME_EXPORTAS {
169            data.read_string()
170                .map(ByteString)
171                .map(Some)
172                .read_error("Could not read COFF import library export name")?
173        } else {
174            None
175        };
176        Ok(ImportObjectData {
177            symbol,
178            dll,
179            export,
180        })
181    }
182
183    /// The type of import.
184    ///
185    /// This is one of the `IMPORT_OBJECT_*` constants.
186    pub fn import_type(&self) -> u16 {
187        self.name_type.get(LE) & pe::IMPORT_OBJECT_TYPE_MASK
188    }
189
190    /// The type of import name.
191    ///
192    /// This is one of the `IMPORT_OBJECT_*` constants.
193    pub fn name_type(&self) -> u16 {
194        (self.name_type.get(LE) >> pe::IMPORT_OBJECT_NAME_SHIFT) & pe::IMPORT_OBJECT_NAME_MASK
195    }
196}
197
198/// The data following [`pe::ImportObjectHeader`].
199#[derive(Debug, Clone)]
200pub struct ImportObjectData<'data> {
201    symbol: ByteString<'data>,
202    dll: ByteString<'data>,
203    export: Option<ByteString<'data>>,
204}
205
206impl<'data> ImportObjectData<'data> {
207    /// The public symbol name.
208    pub fn symbol(&self) -> &'data [u8] {
209        self.symbol.0
210    }
211
212    /// The name of the DLL to import the symbol from.
213    pub fn dll(&self) -> &'data [u8] {
214        self.dll.0
215    }
216
217    /// The name exported from the DLL.
218    ///
219    /// This is only set if the name is not derived from the symbol name.
220    pub fn export(&self) -> Option<&'data [u8]> {
221        self.export.map(|export| export.0)
222    }
223}