1pub mod exports;
24pub mod header;
25pub mod imports;
26pub mod reloc;
27pub mod sections;
28
29use std::collections::BTreeMap;
30
31use crate::emulator::{mmu::Mmu, Trap};
32use crate::win32::{HostState, Registry};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum PeError {
37 TooSmall { got: usize, need: usize },
39 NotMz,
41 BadELfanew { offset: u32, file_len: usize },
43 NotPe,
45 Pe32PlusUnsupported,
47 BadOptionalHeaderMagic { magic: u16 },
49 UnsupportedMachine { machine: u16 },
51 DirectoryOutOfRange {
53 name: &'static str,
54 rva: u32,
55 size: u32,
56 },
57 ManagedPe,
59 UnknownImportDll { dll: String },
62 UnknownImportFunction { dll: String, name: String },
64 SectionOutOfRange {
67 name: String,
68 raw_off: u32,
69 raw_size: u32,
70 },
71 BadRelocBlock { rva: u32, reason: &'static str },
73 Trap(Trap),
76}
77
78impl core::fmt::Display for PeError {
79 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80 match self {
81 PeError::TooSmall { got, need } => {
82 write!(f, "PE file too small: {got} bytes, need ≥ {need}")
83 }
84 PeError::NotMz => f.write_str("missing 'MZ' DOS signature"),
85 PeError::BadELfanew { offset, file_len } => {
86 write!(f, "e_lfanew {offset:#x} outside file (len {file_len})")
87 }
88 PeError::NotPe => f.write_str("missing 'PE\\0\\0' signature"),
89 PeError::Pe32PlusUnsupported => f.write_str("PE32+ (64-bit) not supported"),
90 PeError::BadOptionalHeaderMagic { magic } => {
91 write!(f, "bad optional-header magic {magic:#x}")
92 }
93 PeError::UnsupportedMachine { machine } => {
94 write!(f, "machine {machine:#x} is not IMAGE_FILE_MACHINE_I386")
95 }
96 PeError::DirectoryOutOfRange { name, rva, size } => {
97 write!(
98 f,
99 "directory {name} (rva {rva:#x}, size {size}) out of image range"
100 )
101 }
102 PeError::ManagedPe => f.write_str("managed (.NET) PE not supported"),
103 PeError::UnknownImportDll { dll } => {
104 write!(f, "no Round-1 stub registry entry for DLL '{dll}'")
105 }
106 PeError::UnknownImportFunction { dll, name } => {
107 write!(f, "no stub for {dll}!{name}")
108 }
109 PeError::SectionOutOfRange {
110 name,
111 raw_off,
112 raw_size,
113 } => {
114 write!(
115 f,
116 "section '{name}' raw bytes [{raw_off:#x}..+{raw_size}] out of file"
117 )
118 }
119 PeError::BadRelocBlock { rva, reason } => {
120 write!(f, "malformed reloc block at rva {rva:#x}: {reason}")
121 }
122 PeError::Trap(t) => write!(f, "MMU trap during load: {t}"),
123 }
124 }
125}
126
127impl From<Trap> for PeError {
128 fn from(t: Trap) -> Self {
129 PeError::Trap(t)
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct Image {
136 pub name: String,
138 pub image_base: u32,
142 pub entry_point: u32,
145 pub size_of_image: u32,
148 pub sections: Vec<sections::Section>,
150 pub exports: BTreeMap<String, u32>,
153}
154
155impl Image {
156 pub fn export(&self, name: &str) -> Option<u32> {
158 self.exports
159 .get(name)
160 .map(|rva| self.image_base.wrapping_add(*rva))
161 }
162}
163
164#[derive(Debug, Default, Clone)]
166pub struct LoadOptions {
167 pub imports: imports::ResolveMode,
172 pub fail_soft_log: Option<Vec<(String, String)>>,
176 pub target_image_base: Option<u32>,
185}
186
187impl Default for imports::ResolveMode {
188 fn default() -> Self {
189 Self::Strict
190 }
191}
192
193pub struct Loader<'a> {
195 mmu: &'a mut Mmu,
196 registry: &'a mut Registry,
197 host: &'a mut HostState,
198}
199
200impl<'a> Loader<'a> {
201 pub fn new(mmu: &'a mut Mmu, registry: &'a mut Registry, host: &'a mut HostState) -> Self {
202 Loader {
203 mmu,
204 registry,
205 host,
206 }
207 }
208
209 pub fn load(&mut self, name: &str, bytes: &[u8]) -> Result<Image, PeError> {
213 self.load_with_options(name, bytes, &mut LoadOptions::default())
214 }
215
216 pub fn load_with_options(
221 &mut self,
222 name: &str,
223 bytes: &[u8],
224 options: &mut LoadOptions,
225 ) -> Result<Image, PeError> {
226 let parsed = header::parse(bytes)?;
227
228 let preferred_base = parsed.optional.image_base;
229 let load_base = options.target_image_base.unwrap_or(preferred_base);
230 let delta = load_base.wrapping_sub(preferred_base);
231
232 let secs = sections::map_sections_at(self.mmu, &parsed, bytes, load_base)?;
234
235 if delta != 0 {
242 reloc::apply(self.mmu, &parsed, load_base, delta)?;
243 }
244
245 imports::resolve_with(
247 self.mmu,
248 &parsed,
249 load_base,
250 self.registry,
251 options.imports,
252 options.fail_soft_log.as_mut(),
253 )?;
254
255 let exports = exports::parse_exports(&parsed, bytes, load_base)?;
257
258 for s in &secs {
262 sections::apply_section_permissions(self.mmu, s);
263 }
264
265 let image = Image {
266 name: name.to_string(),
267 image_base: load_base,
268 entry_point: load_base.wrapping_add(parsed.optional.address_of_entry_point),
269 size_of_image: parsed.optional.size_of_image,
270 sections: secs,
271 exports,
272 };
273
274 self.host
277 .modules
278 .insert(name.to_ascii_lowercase(), load_base);
279
280 let rsrc = parsed.optional.data_directories[2];
284 if rsrc.virtual_address != 0 && rsrc.size != 0 {
285 self.host
286 .module_resource_dirs
287 .insert(load_base, load_base.wrapping_add(rsrc.virtual_address));
288 }
289
290 Ok(image)
291 }
292}
293
294pub mod test_image;
300
301#[cfg(test)]
302mod tests {
303 use super::test_image::build_minimal_dll;
304 use super::*;
305 use crate::win32::HostState;
306
307 #[test]
308 fn load_minimal_synthesised_dll_succeeds() {
309 let bytes = build_minimal_dll();
310 let mut mmu = Mmu::new();
311 let mut registry = Registry::new();
312 registry.register_kernel32();
313 let mut host = HostState::new(0x6000_0000, 0x7000_0000);
314 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
315 let img = loader.load("synth.dll", &bytes).unwrap();
316 assert_eq!(img.image_base, 0x1000_0000);
317 assert!(mmu.fetch_x8(img.entry_point).is_ok());
319 }
320
321 #[test]
322 fn rejects_non_mz() {
323 let bytes = vec![0u8; 1024];
324 let mut mmu = Mmu::new();
325 let mut registry = Registry::new();
326 let mut host = HostState::new(0, 0);
327 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
328 match loader.load("bad.dll", &bytes) {
329 Err(PeError::NotMz) => (),
330 other => panic!("expected NotMz, got {other:?}"),
331 }
332 }
333
334 #[test]
335 fn rejects_pe32_plus() {
336 let mut bytes = build_minimal_dll();
337 let pe_off =
339 u32::from_le_bytes([bytes[0x3C], bytes[0x3D], bytes[0x3E], bytes[0x3F]]) as usize;
340 let opt_magic_off = pe_off + 4 + 20; bytes[opt_magic_off] = 0x0B;
342 bytes[opt_magic_off + 1] = 0x02;
343 let mut mmu = Mmu::new();
344 let mut registry = Registry::new();
345 let mut host = HostState::new(0, 0);
346 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
347 match loader.load("bad.dll", &bytes) {
348 Err(PeError::Pe32PlusUnsupported) => (),
349 other => panic!("expected Pe32PlusUnsupported, got {other:?}"),
350 }
351 }
352
353 #[test]
354 fn rejects_managed_pe() {
355 let mut bytes = build_minimal_dll();
356 let pe_off =
358 u32::from_le_bytes([bytes[0x3C], bytes[0x3D], bytes[0x3E], bytes[0x3F]]) as usize;
359 let dirs_off = pe_off + 4 + 20 + 96;
362 let comheader_off = dirs_off + 14 * 8;
363 bytes[comheader_off..comheader_off + 4].copy_from_slice(&1u32.to_le_bytes());
364 bytes[comheader_off + 4..comheader_off + 8].copy_from_slice(&8u32.to_le_bytes());
365 let mut mmu = Mmu::new();
366 let mut registry = Registry::new();
367 let mut host = HostState::new(0, 0);
368 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
369 match loader.load("bad.dll", &bytes) {
370 Err(PeError::ManagedPe) => (),
371 other => panic!("expected ManagedPe, got {other:?}"),
372 }
373 }
374
375 #[test]
376 fn export_by_name_resolves_to_va() {
377 let bytes = build_minimal_dll();
378 let mut mmu = Mmu::new();
379 let mut registry = Registry::new();
380 registry.register_kernel32();
381 let mut host = HostState::new(0x6000_0000, 0x7000_0000);
382 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
383 let img = loader.load("synth.dll", &bytes).unwrap();
384 let p = img.export("DllMain").expect("DllMain export");
386 assert_eq!(p, img.entry_point);
387 }
388
389 #[test]
390 fn iat_is_populated_with_thunks() {
391 let bytes = build_minimal_dll();
392 let mut mmu = Mmu::new();
393 let mut registry = Registry::new();
394 registry.register_kernel32();
395 let mut host = HostState::new(0x6000_0000, 0x7000_0000);
396 let mut loader = Loader::new(&mut mmu, &mut registry, &mut host);
397 let img = loader.load("synth.dll", &bytes).unwrap();
398 let expected = registry.resolve("kernel32.dll", "GetProcessHeap").unwrap();
401 let iat = mmu
404 .load32(img.image_base + super::test_image::IAT_RVA)
405 .unwrap();
406 assert_eq!(iat, expected);
407 }
408}