Skip to content

Commit 00d5875

Browse files
committed
Extend CustomCodeMemory interface allowing for using previously-mapped images
`Module::deserialize_raw` combined with `CustomCodeMemory` were introduced with aim of supporting `no_std` platforms. With small tweaks they can also be used for using Wasmtime on platforms that have full-blown virtual memory capabilities, but doesn't allow for directly mapping executable pages from user code instead limiting that capability for system loader. Adding features necessary for such platforms was previously attempted by #8245. There are currently two issues with using `Module::deserialize_raw` for using images either statically linked into embedder executable or dynamically loaded shared objects: - `CodeMemory::publish` will initially make entire image read-only, destroying executable permissions that cannot be restored by user code. This will happen even if `CustomCodeMemory` is provided. - `CodeMemory::publish` will attempt to unconditionally register unwind information. As these are already properly registered by the system loader this is superfluous at best, or could fail module loading if system decides to return error on attempt for double-registration. This commit solves these issues by: - Moving responsibility for making image RO to `CustomCodeMemory` hook. - Making `CustomCodeMemory` publishing hook return enum that tells what steps (only mapping, or mapping with registartion), using default implementation only for actions not reported by the hook. - Additionally `CustomCodeMemory` hooks also receive pointers to the entire image, not only executable section. This allows the embedder to keep track of the images and unload them in case they were loaded as dynamic shared object.
1 parent be0ba4b commit 00d5875

File tree

3 files changed

+140
-70
lines changed

3 files changed

+140
-70
lines changed

crates/wasmtime/src/engine.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use crate::prelude::*;
33
#[cfg(feature = "runtime")]
44
pub use crate::runtime::code_memory::CustomCodeMemory;
55
#[cfg(feature = "runtime")]
6+
pub use crate::runtime::code_memory::HandledByCustomCodeMemory;
7+
#[cfg(feature = "runtime")]
68
use crate::runtime::type_registry::TypeRegistry;
79
#[cfg(feature = "runtime")]
810
use crate::runtime::vm::GcRuntime;

crates/wasmtime/src/runtime/code_memory.rs

Lines changed: 116 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ impl Drop for CodeMemory {
4545
// original (non-executable) state of the memory.
4646
if let Some(mem) = self.custom_code_memory.as_ref() {
4747
if self.published && self.needs_executable {
48-
let text = self.text();
49-
mem.unpublish_executable(text.as_ptr(), text.len())
50-
.expect("Executable memory unpublish failed");
48+
mem.unpublish_image(
49+
self.mmap.as_ptr(),
50+
self.mmap.len(),
51+
if self.needs_executable {
52+
Some(self.text.clone())
53+
} else {
54+
None
55+
},
56+
)
57+
.expect("Executable memory unpublish failed");
5158
}
5259
}
5360

@@ -64,6 +71,23 @@ fn _assert() {
6471
_assert_send_sync::<CodeMemory>();
6572
}
6673

74+
#[derive(PartialEq, PartialOrd)]
75+
/// Actions that were performed by `CustomCodeMemory::publish_image` hook.
76+
pub enum HandledByCustomCodeMemory {
77+
/// Nothing was performed, publishing will be done by the
78+
/// default Wasmtime implementation.
79+
Nothing,
80+
81+
/// Only memory mapping was performed by the hook,
82+
/// registering unwind and debug information will
83+
/// be done by default Wasmtime implementation.
84+
Mapping,
85+
86+
/// Everything was performed by the hook and no further
87+
/// publishing actions are required.
88+
MappingAndRegistration,
89+
}
90+
6791
/// Interface implemented by an embedder to provide custom
6892
/// implementations of code-memory protection and execute permissions.
6993
pub trait CustomCodeMemory: Send + Sync {
@@ -75,30 +99,50 @@ pub trait CustomCodeMemory: Send + Sync {
7599
/// of virtual memory are disabled.
76100
fn required_alignment(&self) -> usize;
77101

78-
/// Publish a region of memory as executable.
102+
/// Publish an image.
79103
///
80-
/// This should update permissions from the default RW
81-
/// (readable/writable but not executable) to RX
82-
/// (readable/executable but not writable), enforcing W^X
83-
/// discipline.
104+
/// This should ensure that the provided image region is mapped
105+
/// read-only, with the exception of `code` area that (if provided)
106+
/// should be mapped as readable and executable.
107+
///
108+
/// Return value describes which actions were completed, while other
109+
/// steps will be handled by Wasmtime default implementation.
84110
///
85111
/// If the platform requires any data/instruction coherence
86112
/// action, that should be performed as part of this hook as well.
87113
///
88-
/// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
89-
/// per `required_alignment()`.
90-
fn publish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
91-
92-
/// Unpublish a region of memory.
114+
/// If `image` is the image provided to `Module::deserialize_raw`
115+
/// which was already prepared for execution, this hook might
116+
/// be a no-op reporting all actions as completed.
93117
///
94-
/// This should perform the opposite effect of `make_executable`,
95-
/// switching a range of memory back from RX (readable/executable)
96-
/// to RW (readable/writable). It is guaranteed that no code is
118+
/// Values in `code` range are relative to `image` pointer.
119+
/// `code` is guaranteed to be aligned as per `required_alignment()`.
120+
fn publish_image(
121+
&self,
122+
image: *const u8,
123+
len: usize,
124+
code: Option<Range<usize>>,
125+
) -> anyhow::Result<HandledByCustomCodeMemory>;
126+
127+
/// Unpublish an image.
128+
///
129+
/// This should perform the opposite effect of `publish_image`.
130+
///
131+
/// If `image` is the image provided to `Module::deserialize_raw`,
132+
/// this hook might be used for unloading that image from address space.
133+
///
134+
/// Otherwise it should switch a range of memory back to
135+
/// readable and writable. It is guaranteed that no code is
97136
/// running anymore from this region.
98137
///
99-
/// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
100-
/// per `required_alignment()`.
101-
fn unpublish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
138+
/// Values in `code` range are relative to `image` pointer.
139+
/// `code` is guaranteed to be aligned as per `required_alignment()`.
140+
fn unpublish_image(
141+
&self,
142+
image: *const u8,
143+
len: usize,
144+
code: Option<Range<usize>>,
145+
) -> anyhow::Result<()>;
102146
}
103147

104148
impl CodeMemory {
@@ -297,25 +341,29 @@ impl CodeMemory {
297341
// both the actual unwinding tables as well as the validity of the
298342
// pointers we pass in itself.
299343
unsafe {
300-
// Next freeze the contents of this image by making all of the
301-
// memory readonly. Nothing after this point should ever be modified
302-
// so commit everything. For a compiled-in-memory image this will
303-
// mean IPIs to evict writable mappings from other cores. For
304-
// loaded-from-disk images this shouldn't result in IPIs so long as
305-
// there weren't any relocations because nothing should have
306-
// otherwise written to the image at any point either.
307-
//
308-
// Note that if virtual memory is disabled this is skipped because
309-
// we aren't able to make it readonly, but this is just a
310-
// defense-in-depth measure and isn't required for correctness.
311-
#[cfg(has_virtual_memory)]
312-
if self.mmap.supports_virtual_memory() {
313-
self.mmap.make_readonly(0..self.mmap.len())?;
314-
}
344+
// Potentially call embedder-provided custom publishing implementation,
345+
// and only perform publishing steps that weren't done by it.
346+
let handled = self.custom_publish()?;
347+
348+
if handled < HandledByCustomCodeMemory::Mapping {
349+
// Next freeze the contents of this image by making all of the
350+
// memory readonly. Nothing after this point should ever be modified
351+
// so commit everything. For a compiled-in-memory image this will
352+
// mean IPIs to evict writable mappings from other cores. For
353+
// loaded-from-disk images this shouldn't result in IPIs so long as
354+
// there weren't any relocations because nothing should have
355+
// otherwise written to the image at any point either.
356+
//
357+
// Note that if virtual memory is disabled this is skipped because
358+
// we aren't able to make it readonly, but this is just a
359+
// defense-in-depth measure and isn't required for correctness.
360+
#[cfg(has_virtual_memory)]
361+
if self.mmap.supports_virtual_memory() {
362+
self.mmap.make_readonly(0..self.mmap.len())?;
363+
}
315364

316-
// Switch the executable portion from readonly to read/execute.
317-
if self.needs_executable {
318-
if !self.custom_publish()? {
365+
// Switch the executable portion from readonly to read/execute.
366+
if self.needs_executable {
319367
if !self.mmap.supports_virtual_memory() {
320368
bail!("this target requires virtual memory to be enabled");
321369
}
@@ -343,39 +391,45 @@ impl CodeMemory {
343391
}
344392
}
345393

346-
// With all our memory set up use the platform-specific
347-
// `UnwindRegistration` implementation to inform the general
348-
// runtime that there's unwinding information available for all
349-
// our just-published JIT functions.
350-
self.register_unwind_info()?;
394+
if handled < HandledByCustomCodeMemory::MappingAndRegistration {
395+
// With all our memory set up use the platform-specific
396+
// `UnwindRegistration` implementation to inform the general
397+
// runtime that there's unwinding information available for all
398+
// our just-published JIT functions.
399+
self.register_unwind_info()?;
351400

352-
#[cfg(feature = "debug-builtins")]
353-
self.register_debug_image()?;
401+
#[cfg(feature = "debug-builtins")]
402+
self.register_debug_image()?;
403+
}
354404
}
355405

356406
Ok(())
357407
}
358408

359-
fn custom_publish(&mut self) -> Result<bool> {
409+
fn custom_publish(&mut self) -> Result<HandledByCustomCodeMemory> {
360410
if let Some(mem) = self.custom_code_memory.as_ref() {
361-
let text = self.text();
362-
// The text section should be aligned to
363-
// `custom_code_memory.required_alignment()` due to a
364-
// combination of two invariants:
365-
//
366-
// - MmapVec aligns its start address, even in owned-Vec mode; and
367-
// - The text segment inside the ELF image will be aligned according
368-
// to the platform's requirements.
369-
let text_addr = text.as_ptr() as usize;
370-
assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
371-
372-
// The custom code memory handler will ensure the
373-
// memory is executable and also handle icache
374-
// coherence.
375-
mem.publish_executable(text.as_ptr(), text.len())?;
376-
Ok(true)
411+
if self.needs_executable {
412+
// The text section should be aligned to
413+
// `custom_code_memory.required_alignment()` due to a
414+
// combination of two invariants:
415+
//
416+
// - MmapVec aligns its start address, even in owned-Vec mode; and
417+
// - The text segment inside the ELF image will be aligned according
418+
// to the platform's requirements.
419+
assert_eq!(
420+
(self.text().as_ptr() as usize) & (mem.required_alignment() - 1),
421+
0
422+
);
423+
424+
// The custom code memory handler will ensure the
425+
// memory is executable and also handle icache
426+
// coherence.
427+
mem.publish_image(self.mmap.as_ptr(), self.mmap.len(), Some(self.text.clone()))
428+
} else {
429+
mem.publish_image(self.mmap.as_ptr(), self.mmap.len(), None)
430+
}
377431
} else {
378-
Ok(false)
432+
Ok(HandledByCustomCodeMemory::Nothing)
379433
}
380434
}
381435

tests/all/custom_code_memory.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(all(not(target_os = "windows"), not(miri)))]
22
mod not_for_windows {
3+
use core::ops::Range;
34
use rustix::mm::{MprotectFlags, mprotect};
45
use rustix::param::page_size;
56
use std::sync::Arc;
@@ -11,18 +12,31 @@ mod not_for_windows {
1112
page_size()
1213
}
1314

14-
fn publish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()> {
15+
fn publish_image(
16+
&self,
17+
ptr: *const u8,
18+
len: usize,
19+
code: Option<Range<usize>>,
20+
) -> anyhow::Result<HandledByCustomCodeMemory> {
1521
unsafe {
16-
mprotect(
17-
ptr as *mut _,
18-
len,
19-
MprotectFlags::READ | MprotectFlags::EXEC,
20-
)?;
22+
mprotect(ptr as *mut _, len, MprotectFlags::READ)?;
23+
if let Some(code) = code {
24+
mprotect(
25+
ptr.add(code.start) as *mut _,
26+
code.end - code.start,
27+
MprotectFlags::READ | MprotectFlags::EXEC,
28+
)?;
29+
}
2130
}
22-
Ok(())
31+
Ok(HandledByCustomCodeMemory::Mapping)
2332
}
2433

25-
fn unpublish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()> {
34+
fn unpublish_image(
35+
&self,
36+
ptr: *const u8,
37+
len: usize,
38+
_code: Option<Range<usize>>,
39+
) -> anyhow::Result<()> {
2640
unsafe {
2741
mprotect(
2842
ptr as *mut _,

0 commit comments

Comments
 (0)