Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push qyxoowpvqtrw #83

Merged
merged 6 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ fn main() -> anyhow::Result<ExitCode> {
.collect::<Vec<_>>()
.join(" ");
let mut machine = win32::Machine::new(Box::new(host.clone()));
machine.set_external_dlls(&args.external_dll);
machine.set_external_dlls(args.external_dll);
machine.state.winmm.audio_enabled = args.audio;

let addrs = machine
Expand Down
2 changes: 1 addition & 1 deletion web/glue/src/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub enum Status {
#[wasm_bindgen]
impl Emulator {
pub fn set_external_dlls(&mut self, dlls: Vec<String>) {
self.machine.set_external_dlls(&dlls);
self.machine.set_external_dlls(dlls);
}

pub fn load_exe(&mut self, buf: &[u8], cmdline: String, relocate: bool) -> JsResult<()> {
Expand Down
197 changes: 169 additions & 28 deletions win32/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
//! PE image loading.

use crate::{machine::Machine, winapi};
//!
//! The two main entry points are load_exe and load_dll.
//! These call into a shared load_module, which does the PE loading
//! as well as recursively resolving imported modules.

use crate::{
host,
machine::Machine,
winapi::{self, builtin::BuiltinDLL, kernel32::HMODULE},
};
use memory::{Extensions, ExtensionsMut};
use std::{collections::HashMap, path::Path};
use typed_path::WindowsPath;

/// Create a memory mapping, optionally copying some data to it.
fn map_memory(machine: &mut Machine, mapping: winapi::kernel32::Mapping, buf: Option<&[u8]>) {
Expand All @@ -25,7 +34,7 @@ fn map_memory(machine: &mut Machine, mapping: winapi::kernel32::Mapping, buf: Op
/// Copy the file header itself into memory, choosing a base address.
fn load_image(
machine: &mut Machine,
filename: &str,
module_name: &str,
file: &pe::File,
buf: &[u8],
relocate: Option<Option<u32>>,
Expand All @@ -46,7 +55,7 @@ fn load_image(
winapi::kernel32::Mapping {
addr,
size: first_page_size as u32,
desc: filename.into(),
desc: module_name.into(),
flags: pe::IMAGE_SCN::MEM_READ,
},
Some(&buf[..first_page_size]),
Expand All @@ -58,7 +67,7 @@ fn load_image(
/// Load a PE section into memory.
fn load_section(
machine: &mut Machine,
filename: &str,
module_name: &str,
base: u32,
buf: &[u8],
sec: &pe::IMAGE_SECTION_HEADER,
Expand Down Expand Up @@ -93,7 +102,7 @@ fn load_section(
addr: dst,
size: mapping_size,
desc: format!(
"{filename} {:?} ({:?})",
"{module_name} {:?} ({:?})",
sec.name().unwrap_or("[invalid]"),
flags
),
Expand Down Expand Up @@ -133,7 +142,7 @@ fn load_exports(image_base: u32, image: &[u8], exports_data: &[u8]) -> Exports {
}
}

fn patch_iat(machine: &mut Machine, base: u32, imports_addr: &pe::IMAGE_DATA_DIRECTORY) {
fn load_imports(machine: &mut Machine, base: u32, imports_addr: &pe::IMAGE_DATA_DIRECTORY) {
// Traverse the ILT, gathering up addresses that need to be fixed up to point at
// the correct targets.
let mut patches: Vec<(u32, u32)> = Vec::new();
Expand All @@ -152,15 +161,15 @@ fn patch_iat(machine: &mut Machine, base: u32, imports_addr: &pe::IMAGE_DATA_DIR
};
let dll_name = dll_name.to_ascii_lowercase();
let hmodule = winapi::kernel32::load_library(machine, &dll_name);
let mut dll = machine.state.kernel32.dlls.get_mut(&hmodule);
let mut module = machine.state.kernel32.modules.get_mut(&hmodule);
for (i, entry) in dll_imports.ilt(image).enumerate() {
let sym = entry.as_import_symbol(image);
let name = format!("{}!{}", dll_name, sym.to_string());
let iat_addr = base + dll_imports.iat_offset() + (i as u32 * 4);
machine.labels.insert(iat_addr, format!("{}@IAT", name));

let resolved_addr = if let Some(dll) = dll.as_mut() {
if let Some(sym) = dll.resolve(&sym) {
let resolved_addr = if let Some(module) = module.as_deref_mut() {
if let Some(sym) = module.exports.resolve(&sym) {
Some(sym)
} else {
log::warn!("missing symbol {name}");
Expand All @@ -180,18 +189,27 @@ fn patch_iat(machine: &mut Machine, base: u32, imports_addr: &pe::IMAGE_DATA_DIR
}
}

/// Load an exe or dll, including resolving imports.
pub fn load_module(
/// TODO: any direct user of this misses out on the aliasing performed in resolve_dll.
pub fn normalize_module_name(mut name: String) -> String {
name.make_ascii_lowercase();
if !name.ends_with(".dll") && !name.ends_with(".") {
name.push_str(".dll");
}
name
}

/// Load an exe or dll without resolving its imports.
fn load_one_module(
machine: &mut Machine,
filename: &str,
module_name: String,
buf: &[u8],
file: &pe::File,
relocate: Option<Option<u32>>,
) -> anyhow::Result<Module> {
let base = load_image(machine, filename, file, buf, relocate);
let base = load_image(machine, &module_name, file, buf, relocate);

for sec in file.sections.iter() {
load_section(machine, filename, base, buf, sec);
load_section(machine, &module_name, base, buf, sec);
}

if base != file.opt_header.ImageBase {
Expand All @@ -217,8 +235,6 @@ pub fn load_module(
}
}

// Gather exports before loading imports, because the import process may need to load
// DLLs which reference exports from this module.
let image = machine.emu.memory.mem().slice(base..);
let exports = if let Some(dir) = file.get_data_directory(pe::IMAGE_DIRECTORY_ENTRY::EXPORT) {
let section = dir
Expand All @@ -227,18 +243,14 @@ pub fn load_module(
let exports = load_exports(base, image, section);
for (name, &addr) in exports.names.iter() {
if let Some(name) = name.try_ascii() {
machine.labels.insert(addr, format!("{filename}!{name}"));
machine.labels.insert(addr, format!("{module_name}!{name}"));
}
}
exports
} else {
Exports::default()
};

if let Some(imports) = file.get_data_directory(pe::IMAGE_DIRECTORY_ENTRY::IMPORT) {
patch_iat(machine, base, imports);
}

let resources = file
.data_directory
.get(pe::IMAGE_DIRECTORY_ENTRY::RESOURCE as usize)
Expand All @@ -251,13 +263,36 @@ pub fn load_module(
};

Ok(Module {
name: module_name,
image_base: base,
exports,
resources,
entry_point,
})
}

/// Load an exe or dll, inserting it into the module map and resolving its imports.
fn load_module(
machine: &mut Machine,
module_name: String,
buf: &[u8],
file: &pe::File,
relocate: Option<Option<u32>>,
) -> anyhow::Result<HMODULE> {
let module = load_one_module(machine, module_name, buf, file, relocate)?;
let image_base = module.image_base;

// Register module before loading imports, because imports may refer back to this module.
let hmodule = HMODULE::from_raw(module.image_base);
machine.state.kernel32.modules.insert(hmodule, module);

if let Some(imports) = file.get_data_directory(pe::IMAGE_DIRECTORY_ENTRY::IMPORT) {
load_imports(machine, image_base, imports);
}

Ok(hmodule)
}

pub struct EXEFields {
pub entry_point: u32,
pub stack_size: u32,
Expand All @@ -272,11 +307,13 @@ pub fn load_exe(
let file = pe::parse(buf)?;
let path = Path::new(path);
let filename = path.file_name().unwrap().to_string_lossy();
let module = load_module(machine, &filename, buf, &file, relocate)?;
let module_name = normalize_module_name(filename.into_owned());
let hmodule = load_module(machine, module_name, buf, &file, relocate)?;
let module = machine.state.kernel32.modules.get(&hmodule).unwrap();
machine.state.kernel32.image_base = module.image_base;

if let Some(res_data) = module.resources {
machine.state.kernel32.resources = res_data;
if let Some(res_data) = &module.resources {
machine.state.kernel32.resources = res_data.clone();
}

let addrs = EXEFields {
Expand All @@ -289,6 +326,9 @@ pub fn load_exe(
/// The result of loading an exe or DLL.
#[derive(Debug, Default)]
pub struct Module {
/// Normalized module name, e.g. "kernel32.dll".
pub name: String,

/// Address where the module was loaded.
pub image_base: u32,

Expand All @@ -309,7 +349,108 @@ pub struct Exports {
pub fns: Vec<u32>,
}

pub fn load_dll(machine: &mut Machine, filename: &str, buf: &[u8]) -> anyhow::Result<Module> {
let file = pe::parse(buf)?;
load_module(machine, filename, buf, &file, Some(None))
impl Exports {
pub fn resolve(&mut self, sym: &pe::ImportSymbol) -> Option<u32> {
match *sym {
pe::ImportSymbol::Name(name) => self.names.get(name).copied(),
pe::ImportSymbol::Ordinal(ord) => {
self.fns.get((ord - self.ordinal_base) as usize).copied()
}
}
}
}

enum DLLResolution {
Builtin(&'static BuiltinDLL),
External(String),
}

/// Given an imported DLL name, find the name of the DLL file we'll load for it.
/// Handles normalizing the name, aliases, and builtins.
fn resolve_dll(machine: &mut Machine, filename: String) -> anyhow::Result<DLLResolution> {
let mut filename = normalize_module_name(filename);
if filename.starts_with("api-") {
match winapi::builtin::apiset(&filename) {
Some(name) => filename = name.to_string(),
None => anyhow::bail!("unknown apiset {filename}"),
}
}

let use_external = machine.external_dlls.contains(&filename);
if !use_external {
if let Some(alias) = winapi::builtin::dll_alias(&filename) {
filename = alias.to_string();
}
if let Some(builtin) = winapi::builtin::DLLS
.iter()
.find(|&dll| dll.file_name == filename)
{
return Ok(DLLResolution::Builtin(builtin));
}
}

Ok(DLLResolution::External(filename))
}

pub fn load_dll(machine: &mut Machine, filename: &str) -> anyhow::Result<HMODULE> {
let res = resolve_dll(machine, filename.to_owned())?;
let module_name = match &res {
DLLResolution::Builtin(builtin) => builtin.file_name,
DLLResolution::External(name) => name,
};

// See if already loaded.
if let Some((&hmodule, _)) = machine
.state
.kernel32
.modules
.iter()
.find(|(_, dll)| dll.name == module_name)
{
return Ok(hmodule);
}

match res {
DLLResolution::Builtin(builtin) => {
let file = pe::parse(builtin.raw)?;
let hmodule = load_module(
machine,
builtin.file_name.to_owned(),
builtin.raw,
&file,
Some(None),
)?;
let module = machine.state.kernel32.modules.get(&hmodule).unwrap();

// For builtins, register all the exports as known symbols.
// It is critical that the DLL's exports match up to the shims array;
// this is ensured by both being generated by the same generator.
for (&addr, shim) in module.exports.fns.iter().zip(builtin.shims) {
machine.emu.shims.register(addr, Ok(shim));
}

Ok(hmodule)
}
DLLResolution::External(filename) => {
let mut buf = Vec::new();
let exe = machine.state.kernel32.cmdline.exe_name();
let exe_dir = exe.rsplitn(2, '\\').last().unwrap();
let dll_paths = [format!("{exe_dir}\\{filename}"), filename.to_string()];
for path in &dll_paths {
let path = WindowsPath::new(path);
let mut file = match machine.host.open(path, host::FileOptions::read()) {
Ok(file) => file,
Err(_) => continue,
};
file.read_to_end(&mut buf).unwrap();
// TODO: close file.
break;
}
if buf.is_empty() {
anyhow::bail!("{filename:?} not found");
}
let file = pe::parse(&buf)?;
load_module(machine, filename, &buf, &file, Some(None))
}
}
}
7 changes: 4 additions & 3 deletions win32/src/machine.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::loader;
use crate::{host, winapi};
use std::collections::HashMap;

Expand Down Expand Up @@ -39,10 +40,10 @@ impl<Emu> MachineX<Emu> {
}
}

pub fn set_external_dlls(&mut self, dlls: &[String]) {
pub fn set_external_dlls(&mut self, dlls: Vec<String>) {
self.external_dlls = dlls
.iter()
.map(|dll| winapi::kernel32::normalize_module_name(dll))
.into_iter()
.map(|dll| loader::normalize_module_name(dll))
.collect();
}
}
Expand Down
Loading