Merge pull request #4381 from roc-lang/windows-rust-platforms

Windows rust platforms
This commit is contained in:
Richard Feldman 2022-10-24 18:27:26 -07:00 committed by GitHub
commit 9bb45f5856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 102 deletions

View File

@ -524,36 +524,33 @@ pub fn rebuild_host(
let swift_host_src = host_input_path.with_file_name("host.swift");
let swift_host_header_src = host_input_path.with_file_name("host.h");
let os = roc_target::OperatingSystem::from(target.operating_system);
let executable_extension = match os {
roc_target::OperatingSystem::Windows => "exe",
roc_target::OperatingSystem::Unix => "",
roc_target::OperatingSystem::Wasi => "",
};
let object_extension = match os {
roc_target::OperatingSystem::Windows => "obj",
roc_target::OperatingSystem::Unix => "o",
roc_target::OperatingSystem::Wasi => "o",
};
let host_dest = if matches!(target.architecture, Architecture::Wasm32) {
if matches!(opt_level, OptLevel::Development) {
host_input_path.with_file_name("host.o")
} else {
host_input_path.with_file_name("host.bc")
}
} else if shared_lib_path.is_some() {
host_input_path
.with_file_name("dynhost")
.with_extension(executable_extension)
} else {
let os = roc_target::OperatingSystem::from(target.operating_system);
if shared_lib_path.is_some() {
let extension = match os {
roc_target::OperatingSystem::Windows => "exe",
roc_target::OperatingSystem::Unix => "",
roc_target::OperatingSystem::Wasi => "",
};
host_input_path
.with_file_name("dynhost")
.with_extension(extension)
} else {
let extension = match os {
roc_target::OperatingSystem::Windows => "obj",
roc_target::OperatingSystem::Unix => "o",
roc_target::OperatingSystem::Wasi => "o",
};
host_input_path
.with_file_name("host")
.with_extension(extension)
}
host_input_path
.with_file_name("host")
.with_extension(object_extension)
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -656,6 +653,7 @@ pub fn rebuild_host(
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
command.arg("--release");
}
let source_file = if shared_lib_path.is_some() {
command.env("RUSTFLAGS", "-C link-dead-code");
command.args(&["--bin", "host"]);
@ -664,13 +662,16 @@ pub fn rebuild_host(
command.arg("--lib");
"src/lib.rs"
};
let output = command.output().unwrap();
validate_output(source_file, "cargo build", output);
if shared_lib_path.is_some() {
// For surgical linking, just copy the dynamically linked rust app.
std::fs::copy(cargo_out_dir.join("host"), &host_dest).unwrap();
let mut exe_path = cargo_out_dir.join("host");
exe_path.set_extension(executable_extension);
std::fs::copy(&exe_path, &host_dest).unwrap();
} else {
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.

View File

@ -1634,7 +1634,7 @@ mod tests {
let dylib_bytes = crate::generate_dylib::create_dylib_elf64(&names).unwrap();
std::fs::write(dir.join("libapp.so"), dylib_bytes).unwrap();
// now we can compile the host (it uses libapp.obj, hence the order here)
// now we can compile the host (it uses libapp.so, hence the order here)
let output = std::process::Command::new(&zig)
.current_dir(dir)
.args(&[

View File

@ -1,7 +1,7 @@
use object::pe;
use object::LittleEndian as LE;
pub(crate) const APP_DLL: &str = "roc-cheaty-lib.dll";
pub(crate) const APP_DLL: &str = "libapp.dll";
fn synthetic_image_export_directory(
name: &str,

View File

@ -5,7 +5,7 @@ use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
use std::cmp::Ordering;
use std::mem;
use std::path::Path;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
mod elf;
@ -55,7 +55,7 @@ pub fn build_and_preprocess_host(
exported_closure_types: Vec<String>,
) {
let dummy_lib = if let target_lexicon::OperatingSystem::Windows = target.operating_system {
host_input_path.with_file_name("libapp.obj")
host_input_path.with_file_name("libapp.dll")
} else {
host_input_path.with_file_name("libapp.so")
};
@ -123,15 +123,87 @@ fn make_dummy_dll_symbols(
custom_names
}
fn generate_dynamic_lib(target: &Triple, custom_names: &[String], dummy_lib_path: &Path) {
if !dummy_lib_is_up_to_date(target, dummy_lib_path, custom_names) {
let bytes = crate::generate_dylib::generate(target, custom_names)
fn generate_dynamic_lib(target: &Triple, dummy_dll_symbols: &[String], dummy_lib_path: &Path) {
if !dummy_lib_is_up_to_date(target, dummy_lib_path, dummy_dll_symbols) {
let bytes = crate::generate_dylib::generate(target, dummy_dll_symbols)
.unwrap_or_else(|e| internal_error!("{e}"));
std::fs::write(dummy_lib_path, &bytes).unwrap_or_else(|e| internal_error!("{e}"))
std::fs::write(dummy_lib_path, &bytes).unwrap_or_else(|e| internal_error!("{e}"));
if let target_lexicon::OperatingSystem::Windows = target.operating_system {
generate_import_library(dummy_lib_path, dummy_dll_symbols);
}
}
}
fn generate_import_library(dummy_lib_path: &Path, custom_names: &[String]) {
let def_file_content = generate_def_file(custom_names).expect("write to string never fails");
let mut def_path = dummy_lib_path.to_owned();
def_path.set_extension("def");
std::fs::write(def_path, def_file_content.as_bytes())
.unwrap_or_else(|e| internal_error!("{e}"));
let mut def_filename = PathBuf::from(generate_dylib::APP_DLL);
def_filename.set_extension("def");
let mut lib_filename = PathBuf::from(generate_dylib::APP_DLL);
lib_filename.set_extension("lib");
let zig = std::env::var("ROC_ZIG").unwrap_or_else(|_| "zig".into());
// use zig to generate the .lib file. Here is a good description of what is in an import library
//
// > https://www.codeproject.com/Articles/1253835/The-Structure-of-import-Library-File-lib
//
// For when we want to do this in-memory in the future. We can also consider using
//
// > https://github.com/messense/implib-rs
let output = std::process::Command::new(&zig)
.current_dir(dummy_lib_path.parent().unwrap())
.args(&[
"dlltool",
"-d",
def_filename.to_str().unwrap(),
"-m",
"i386:x86-64",
"-D",
generate_dylib::APP_DLL,
"-l",
lib_filename.to_str().unwrap(),
])
.output()
.unwrap();
if !output.status.success() {
use std::io::Write;
std::io::stdout().write_all(&output.stdout).unwrap();
std::io::stderr().write_all(&output.stderr).unwrap();
panic!("zig dlltool failed");
}
}
fn generate_def_file(custom_names: &[String]) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut def_file = String::new();
writeln!(def_file, "LIBRARY libapp")?;
writeln!(def_file, "EXPORTS")?;
for (i, name) in custom_names.iter().enumerate() {
// 1-indexed of course...
let index = i + 1;
writeln!(def_file, " {name} @{index}")?;
}
Ok(def_file)
}
fn object_matches_target<'a>(target: &Triple, object: &object::File<'a, &'a [u8]>) -> bool {
use target_lexicon::{Architecture as TLA, OperatingSystem as TLO};

View File

@ -55,7 +55,7 @@ struct PeMetadata {
thunks_start_offset_in_section: usize,
/// Virtual address of the .rdata section
rdata_virtual_address: u32,
dummy_dll_thunk_section_virtual_address: u32,
/// The offset into the file of the .reloc section
reloc_offset_in_file: usize,
@ -108,18 +108,16 @@ impl PeMetadata {
.unwrap();
let dynamic_relocations = DynamicRelocationsPe::new(preprocessed_data);
let thunks_start_offset_in_file =
find_thunks_start_offset(preprocessed_data, &dynamic_relocations);
let rdata_section = dynhost_obj
.sections()
.find(|s| s.name() == Ok(".rdata"))
.unwrap();
let dummy_dll_thunks = find_thunks_start_offset(preprocessed_data, &dynamic_relocations);
let thunks_start_offset_in_file = dummy_dll_thunks.offset_in_file as usize;
let thunks_start_offset_in_section =
thunks_start_offset_in_file - rdata_section.file_range().unwrap().0 as usize;
let dummy_dll_thunk_section_virtual_address =
dummy_dll_thunks.section.virtual_address.get(LE);
let rdata_virtual_address = rdata_section.address() as u32;
let thunks_start_offset_in_section = (dummy_dll_thunks.offset_in_file
- dummy_dll_thunks.section.pointer_to_raw_data.get(LE) as u64)
as usize;
let (reloc_section_index, reloc_section) = dynhost_obj
.sections()
@ -175,7 +173,7 @@ impl PeMetadata {
dynamic_relocations,
thunks_start_offset_in_file,
thunks_start_offset_in_section,
rdata_virtual_address,
dummy_dll_thunk_section_virtual_address,
reloc_offset_in_file,
reloc_section_index,
}
@ -191,6 +189,7 @@ pub(crate) fn preprocess_windows(
_time: bool,
) -> object::read::Result<()> {
let data = open_mmap(host_exe_filename);
let new_sections = [*b".text\0\0\0", *b".rdata\0\0"];
let mut preprocessed = Preprocessor::preprocess(
preprocessed_filename,
@ -214,23 +213,42 @@ pub(crate) fn preprocess_windows(
dir.size.set(LE, new);
}
// clear out the import table entry. we do implicitly assume that our dummy .dll is the last
{
const W: usize = std::mem::size_of::<ImageImportDescriptor>();
let start = md.dynamic_relocations.imports_offset_in_file as usize
+ W * md.dynamic_relocations.dummy_import_index as usize;
for b in preprocessed[start..][..W].iter_mut() {
*b = 0;
}
}
remove_dummy_dll_import_table_entry(&mut preprocessed, &md);
md.write_to_file(metadata_filename);
Ok(())
}
fn remove_dummy_dll_import_table_entry(executable: &mut [u8], md: &PeMetadata) {
const W: usize = std::mem::size_of::<ImageImportDescriptor>();
let dr = &md.dynamic_relocations;
// there is one zeroed-out descriptor at the back
let count = dr.import_directory_size as usize / W - 1;
let descriptors = load_structs_inplace_mut::<ImageImportDescriptor>(
executable,
dr.import_directory_offset_in_file as usize,
count,
);
// move the dummy to the final position
descriptors.swap(dr.dummy_import_index as usize, count - 1);
// make this the new zeroed-out descriptor
if let Some(d) = descriptors.last_mut() {
*d = ImageImportDescriptor {
original_first_thunk: Default::default(),
time_date_stamp: Default::default(),
forwarder_chain: Default::default(),
name: Default::default(),
first_thunk: Default::default(),
}
}
}
pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_bytes: &[u8]) {
let md = PeMetadata::read_from_file(metadata_path);
@ -431,7 +449,10 @@ struct DynamicRelocationsPe {
section_offset_in_file: u32,
/// Offset in the file of the imports directory
imports_offset_in_file: u32,
import_directory_offset_in_file: u32,
/// Size in the file of the imports directory
import_directory_size: u32,
/// Offset in the file of the data directories
data_directories_offset_in_file: u32,
@ -536,8 +557,9 @@ impl DynamicRelocationsPe {
let import_table = ImportTable::new(section_data, section_va, import_va);
let imports_offset_in_section = import_va.wrapping_sub(section_va);
let imports_offset_in_file = offset_in_file + imports_offset_in_section;
let (import_directory_offset_in_file, import_directory_size) = data_dir
.file_range(&sections)
.expect("import directory exists");
let (descriptor, dummy_import_index) = Self::find_roc_dummy_dll(&import_table)?.unwrap();
@ -546,10 +568,11 @@ impl DynamicRelocationsPe {
address_and_offset: Default::default(),
section_virtual_address: section_va,
section_offset_in_file: offset_in_file,
imports_offset_in_file,
import_directory_offset_in_file,
data_directories_offset_in_file,
dummy_import_index,
section_headers_offset_in_file,
import_directory_size,
};
this.append_roc_imports(&import_table, &descriptor)?;
@ -820,12 +843,16 @@ impl Preprocessor {
}
}
}
struct DummyDllThunks<'a> {
section: &'a object::pe::ImageSectionHeader,
offset_in_file: u64,
}
/// Find the place in the executable where the thunks for our dummy .dll are stored
fn find_thunks_start_offset(
executable: &[u8],
fn find_thunks_start_offset<'a>(
executable: &'a [u8],
dynamic_relocations: &DynamicRelocationsPe,
) -> usize {
) -> DummyDllThunks<'a> {
// The host executable contains indirect calls to functions that the host should provide
//
// 14000105d: e8 8e 27 00 00 call 0x1400037f0
@ -854,7 +881,7 @@ fn find_thunks_start_offset(
// - https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail
const W: usize = std::mem::size_of::<ImageImportDescriptor>();
let dummy_import_desc_start = dynamic_relocations.imports_offset_in_file as usize
let dummy_import_desc_start = dynamic_relocations.import_directory_offset_in_file as usize
+ W * dynamic_relocations.dummy_import_index as usize;
let dummy_import_desc =
@ -880,19 +907,21 @@ fn find_thunks_start_offset(
let sections = nt_headers.sections(executable, offset).unwrap();
// find the section the virtual address is in
let (section_va, offset_in_file) = sections
let (section_virtual_address, section) = sections
.iter()
.find_map(|section| {
section
.pe_data_containing(executable, dummy_thunks_address)
.map(|(_section_data, section_va)| {
(section_va, section.pointer_to_raw_data.get(LE))
})
.map(|(_section_data, section_va)| (section_va, section))
})
.expect("Invalid thunk virtual address");
// and get the offset in the file of 0x1400037f0
(dummy_thunks_address - section_va + offset_in_file) as usize
DummyDllThunks {
section,
// and get the offset in the file of 0x1400037f0
offset_in_file: dummy_thunks_address as u64 - section_virtual_address as u64
+ section.pointer_to_raw_data.get(LE) as u64,
}
}
/// Make the thunks point to our actual roc application functions
@ -903,9 +932,10 @@ fn redirect_dummy_dll_functions(
thunks_start_offset: usize,
) {
// it could be that a symbol exposed by the app is not used by the host. We must skip unused symbols
let mut targets = function_definition_vas.iter();
// this is an O(n^2) loop, hopefully that does not become a problem. If it does we can sort
// both vectors to get linear complexity in the loop.
'outer: for (i, host_name) in imports.iter().enumerate() {
for (roc_app_target_name, roc_app_target_va) in targets.by_ref() {
for (roc_app_target_name, roc_app_target_va) in function_definition_vas {
if host_name == roc_app_target_name {
// addresses are 64-bit values
let address_bytes = &mut executable[thunks_start_offset + i * 8..][..8];
@ -1309,8 +1339,8 @@ fn write_image_base_relocation(
/// in a table to find the actual address of the app function. This table must be relocated,
/// because it contains absolute addresses to jump to.
fn relocate_dummy_dll_entries(executable: &mut [u8], md: &PeMetadata) {
let thunks_start_va = (md.rdata_virtual_address - md.image_base as u32)
+ md.thunks_start_offset_in_section as u32;
let thunks_start_va =
md.dummy_dll_thunk_section_virtual_address + md.thunks_start_offset_in_section as u32;
// relocations are defined per 4kb page
const BLOCK_SIZE: u32 = 4096;
@ -1354,17 +1384,20 @@ fn relocate_dummy_dll_entries(executable: &mut [u8], md: &PeMetadata) {
"new .reloc section is too big, and runs into the next section!",
);
// // in the data directories, update the length of the base relocations
// let dir = load_struct_inplace_mut::<pe::ImageDataDirectory>(
// executable,
// md.dynamic_relocations.data_directories_offset_in_file as usize
// + object::pe::IMAGE_DIRECTORY_ENTRY_BASERELOC
// * std::mem::size_of::<pe::ImageDataDirectory>(),
// );
//
// let old_dir_size = dir.size.get(LE);
// debug_assert_eq!(old_section_size, old_dir_size);
// dir.size.set(LE, new_virtual_size);
// in the data directories, update the length of the base relocations
let dir = load_struct_inplace_mut::<pe::ImageDataDirectory>(
executable,
md.dynamic_relocations.data_directories_offset_in_file as usize
+ object::pe::IMAGE_DIRECTORY_ENTRY_BASERELOC
* std::mem::size_of::<pe::ImageDataDirectory>(),
);
// it is crucial that the directory size is rounded up to a multiple of 8!
let old = dir.size.get(LE);
let new = new_virtual_size;
let delta = next_multiple_of((new - old) as usize, 8);
dir.size.set(LE, old + delta as u32);
}
#[cfg(test)]
@ -1554,7 +1587,7 @@ mod test {
remove_dummy_dll_import_table_test(
&mut data,
dynamic_relocations.data_directories_offset_in_file,
dynamic_relocations.imports_offset_in_file,
dynamic_relocations.import_directory_offset_in_file,
dynamic_relocations.dummy_import_index,
);
@ -1696,14 +1729,14 @@ mod test {
// make the dummy dylib based on the app object
let names: Vec<_> = symbols.iter().map(|s| s.name.clone()).collect();
let dylib_bytes = crate::generate_dylib::synthetic_dll(&names);
std::fs::write(dir.join("libapp.obj"), dylib_bytes).unwrap();
std::fs::write(dir.join("libapp.dll"), dylib_bytes).unwrap();
// now we can compile the host (it uses libapp.obj, hence the order here)
// now we can compile the host (it uses libapp.dll, hence the order here)
let output = std::process::Command::new(&zig)
.current_dir(dir)
.args(&[
"build-exe",
"libapp.obj",
"libapp.dll",
"host.zig",
"-lc",
"-target",

View File

@ -1,4 +1,9 @@
fn main() {
#[cfg(not(windows))]
println!("cargo:rustc-link-lib=dylib=app");
#[cfg(windows)]
println!("cargo:rustc-link-lib=dylib=libapp");
println!("cargo:rustc-link-search=.");
}

View File

@ -3,7 +3,7 @@
use core::ffi::c_void;
use roc_std::RocStr;
use std::ffi::CStr;
use std::mem::ManuallyDrop;
use std::io::Write;
use std::os::raw::c_char;
extern "C" {
@ -56,21 +56,11 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
unsafe {
// ManuallyDrop must be used here in order to prevent the RocStr from
// getting dropped as soon as it's no longer referenced anywhere, which
// happens earlier than the libc::write that receives a pointer to its data.
let mut roc_str = ManuallyDrop::new(RocStr::default());
roc_main(&mut roc_str);
let mut roc_str = RocStr::default();
unsafe { roc_main(&mut roc_str) };
let len = roc_str.len();
let str_bytes = roc_str.as_bytes().as_ptr() as *const libc::c_void;
if libc::write(1, str_bytes, len) < 0 {
panic!("Writing to stdout failed!");
}
ManuallyDrop::drop(&mut roc_str)
if let Err(e) = std::io::stdout().write_all(roc_str.as_bytes()) {
panic!("Writing to stdout failed! {:?}", e);
}
// Exit code