* cranelift: Add FlushInstructionCache for AArch64 on Windows This was previously done on #3426 for linux. * wasmtime: Add FlushInstructionCache for AArch64 on Windows This was previously done on #3426 for linux. * cranelift: Add MemoryUse flag to JIT Memory Manager This allows us to keep the icache flushing code self-contained and not leak implementation details. This also changes the windows icache flushing code to only flush pages that were previously unflushed. * Add jit-icache-coherence crate * cranelift: Use `jit-icache-coherence` * wasmtime: Use `jit-icache-coherence` * jit-icache-coherence: Make rustix feature additive Mutually exclusive features cause issues. * wasmtime: Remove rustix from wasmtime-jit We now use it via jit-icache-coherence * Rename wasmtime-jit-icache-coherency crate * Use cfg-if in wasmtime-jit-icache-coherency crate * Use inline instead of inline(always) * Add unsafe marker to clear_cache * Conditionally compile all rustix operations membarrier does not exist on MacOS * Publish `wasmtime-jit-icache-coherence` * Remove explicit windows check This is implied by the target_os = "windows" above * cranelift: Remove len != 0 check This is redundant as it is done in non_protected_allocations_iter * Comment cleanups Thanks @akirilov-arm! * Make clear_cache safe * Rename pipeline_flush to pipeline_flush_mt * Revert "Make clear_cache safe" This reverts commit 21165d81c9030ed9b291a1021a367214d2942c90. * More docs! * Fix pipeline_flush reference on clear_cache * Update more docs! * Move pipeline flush after `mprotect` calls Technically the `clear_cache` operation is a lie in AArch64, so move the pipeline flush after the `mprotect` calls so that it benefits from the implicit cache cleaning done by it. * wasmtime: Remove rustix backend from icache crate * wasmtime: Use libc for macos * wasmtime: Flush icache on all arch's for windows * wasmtime: Add flags to membarrier call
195 lines
7.5 KiB
Rust
195 lines
7.5 KiB
Rust
//! Memory management for executable code.
|
|
|
|
use crate::unwind::UnwindRegistration;
|
|
use anyhow::{bail, Context, Result};
|
|
use object::read::{File, Object, ObjectSection};
|
|
use std::ffi::c_void;
|
|
use std::mem::ManuallyDrop;
|
|
use wasmtime_jit_icache_coherence as icache_coherence;
|
|
use wasmtime_runtime::MmapVec;
|
|
|
|
/// Management of executable memory within a `MmapVec`
|
|
///
|
|
/// This type consumes ownership of a region of memory and will manage the
|
|
/// executable permissions of the contained JIT code as necessary.
|
|
pub struct CodeMemory {
|
|
// NB: these are `ManuallyDrop` because `unwind_registration` must be
|
|
// dropped first since it refers to memory owned by `mmap`.
|
|
mmap: ManuallyDrop<MmapVec>,
|
|
unwind_registration: ManuallyDrop<Option<UnwindRegistration>>,
|
|
published: bool,
|
|
}
|
|
|
|
impl Drop for CodeMemory {
|
|
fn drop(&mut self) {
|
|
// Drop `unwind_registration` before `self.mmap`
|
|
unsafe {
|
|
ManuallyDrop::drop(&mut self.unwind_registration);
|
|
ManuallyDrop::drop(&mut self.mmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn _assert() {
|
|
fn _assert_send_sync<T: Send + Sync>() {}
|
|
_assert_send_sync::<CodeMemory>();
|
|
}
|
|
|
|
/// Result of publishing a `CodeMemory`, containing references to the parsed
|
|
/// internals.
|
|
pub struct Publish<'a> {
|
|
/// The parsed ELF image that resides within the original `MmapVec`.
|
|
pub obj: File<'a>,
|
|
|
|
/// Reference to the entire `MmapVec` and its contents.
|
|
pub mmap: &'a MmapVec,
|
|
|
|
/// Reference to just the text section of the object file, a subslice of
|
|
/// `mmap`.
|
|
pub text: &'a [u8],
|
|
}
|
|
|
|
impl CodeMemory {
|
|
/// Creates a new `CodeMemory` by taking ownership of the provided
|
|
/// `MmapVec`.
|
|
///
|
|
/// The returned `CodeMemory` manages the internal `MmapVec` and the
|
|
/// `publish` method is used to actually make the memory executable.
|
|
pub fn new(mmap: MmapVec) -> Self {
|
|
Self {
|
|
mmap: ManuallyDrop::new(mmap),
|
|
unwind_registration: ManuallyDrop::new(None),
|
|
published: false,
|
|
}
|
|
}
|
|
|
|
/// Returns a reference to the underlying `MmapVec` this memory owns.
|
|
pub fn mmap(&self) -> &MmapVec {
|
|
&self.mmap
|
|
}
|
|
|
|
/// Publishes the internal ELF image to be ready for execution.
|
|
///
|
|
/// This method can only be called once and will panic if called twice. This
|
|
/// will parse the ELF image from the original `MmapVec` and do everything
|
|
/// necessary to get it ready for execution, including:
|
|
///
|
|
/// * Change page protections from read/write to read/execute.
|
|
/// * Register unwinding information with the OS
|
|
///
|
|
/// After this function executes all JIT code should be ready to execute.
|
|
/// The various parsed results of the internals of the `MmapVec` are
|
|
/// returned through the `Publish` structure.
|
|
pub fn publish(&mut self, enable_branch_protection: bool) -> Result<Publish<'_>> {
|
|
assert!(!self.published);
|
|
self.published = true;
|
|
|
|
let mut ret = Publish {
|
|
obj: File::parse(&self.mmap[..])
|
|
.with_context(|| "failed to parse internal compilation artifact")?,
|
|
mmap: &self.mmap,
|
|
text: &[],
|
|
};
|
|
let mmap_ptr = self.mmap.as_ptr() as u64;
|
|
|
|
// Sanity-check that all sections are aligned correctly.
|
|
for section in ret.obj.sections() {
|
|
let data = match section.data() {
|
|
Ok(data) => data,
|
|
Err(_) => continue,
|
|
};
|
|
if section.align() == 0 || data.len() == 0 {
|
|
continue;
|
|
}
|
|
if (data.as_ptr() as u64 - mmap_ptr) % section.align() != 0 {
|
|
bail!(
|
|
"section `{}` isn't aligned to {:#x}",
|
|
section.name().unwrap_or("ERROR"),
|
|
section.align()
|
|
);
|
|
}
|
|
}
|
|
|
|
// Find the `.text` section with executable code in it.
|
|
let text = match ret.obj.section_by_name(".text") {
|
|
Some(section) => section,
|
|
None => return Ok(ret),
|
|
};
|
|
ret.text = match text.data() {
|
|
Ok(data) if !data.is_empty() => data,
|
|
_ => return Ok(ret),
|
|
};
|
|
|
|
// The unsafety here comes from a few things:
|
|
//
|
|
// * First in `apply_reloc` we're walking around the `File` that the
|
|
// `object` crate has to get a mutable view into the text section.
|
|
// Currently the `object` crate doesn't support easily parsing a file
|
|
// and updating small bits and pieces of it, so we work around it for
|
|
// now. ELF's file format should guarantee that `text_mut` doesn't
|
|
// collide with any memory accessed by `text.relocations()`.
|
|
//
|
|
// * Second we're actually updating some page protections to executable
|
|
// memory.
|
|
//
|
|
// * Finally we're registering unwinding information which relies on the
|
|
// correctness of the information in the first place. This applies to
|
|
// both the actual unwinding tables as well as the validity of the
|
|
// pointers we pass in itself.
|
|
unsafe {
|
|
let text_mut =
|
|
std::slice::from_raw_parts_mut(ret.text.as_ptr() as *mut u8, ret.text.len());
|
|
let text_offset = ret.text.as_ptr() as usize - ret.mmap.as_ptr() as usize;
|
|
let text_range = text_offset..text_offset + text_mut.len();
|
|
|
|
// Double-check there are no relocations in the text section. At
|
|
// this time relocations are not expected at all from loaded code
|
|
// since everything should be resolved at compile time. Handling
|
|
// must be added here, though, if relocations pop up.
|
|
assert!(text.relocations().count() == 0);
|
|
|
|
// Clear the newly allocated code from cache if the processor requires it
|
|
//
|
|
// Do this before marking the memory as R+X, technically we should be able to do it after
|
|
// but there are some CPU's that have had errata about doing this with read only memory.
|
|
icache_coherence::clear_cache(ret.text.as_ptr() as *const c_void, ret.text.len())
|
|
.expect("Failed cache clear");
|
|
|
|
// Switch the executable portion from read/write to
|
|
// read/execute, notably not using read/write/execute to prevent
|
|
// modifications.
|
|
self.mmap
|
|
.make_executable(text_range.clone(), enable_branch_protection)
|
|
.expect("unable to make memory executable");
|
|
|
|
// Flush any in-flight instructions from the pipeline
|
|
icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");
|
|
|
|
// With all our memory set up use the platform-specific
|
|
// `UnwindRegistration` implementation to inform the general
|
|
// runtime that there's unwinding information available for all
|
|
// our just-published JIT functions.
|
|
*self.unwind_registration = register_unwind_info(&ret.obj, ret.text)?;
|
|
}
|
|
|
|
Ok(ret)
|
|
}
|
|
}
|
|
|
|
unsafe fn register_unwind_info(obj: &File, text: &[u8]) -> Result<Option<UnwindRegistration>> {
|
|
let unwind_info = match obj
|
|
.section_by_name(UnwindRegistration::section_name())
|
|
.and_then(|s| s.data().ok())
|
|
{
|
|
Some(info) => info,
|
|
None => return Ok(None),
|
|
};
|
|
if unwind_info.len() == 0 {
|
|
return Ok(None);
|
|
}
|
|
Ok(Some(
|
|
UnwindRegistration::new(text.as_ptr(), unwind_info.as_ptr(), unwind_info.len())
|
|
.context("failed to create unwind info registration")?,
|
|
))
|
|
}
|