wasmtime/runtime/
code_memory.rs

1//! Memory management for executable code.
2
3use crate::Engine;
4use crate::prelude::*;
5use crate::runtime::vm::MmapVec;
6use alloc::sync::Arc;
7use core::ops::Range;
8use object::SectionFlags;
9use object::endian::Endianness;
10use object::read::{Object, ObjectSection, elf::ElfFile64};
11use wasmtime_environ::{Trap, lookup_trap_code, obj};
12use wasmtime_unwinder::ExceptionTable;
13
14/// Management of executable memory within a `MmapVec`
15///
16/// This type consumes ownership of a region of memory and will manage the
17/// executable permissions of the contained JIT code as necessary.
18pub struct CodeMemory {
19    mmap: MmapVec,
20    #[cfg(has_host_compiler_backend)]
21    unwind_registration: Option<crate::runtime::vm::UnwindRegistration>,
22    #[cfg(feature = "debug-builtins")]
23    debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
24    published: bool,
25    enable_branch_protection: bool,
26    needs_executable: bool,
27    #[cfg(feature = "debug-builtins")]
28    has_native_debug_info: bool,
29    custom_code_memory: Option<Arc<dyn CustomCodeMemory>>,
30
31    // Ranges within `self.mmap` of where the particular sections lie.
32    text: Range<usize>,
33    unwind: Range<usize>,
34    trap_data: Range<usize>,
35    wasm_data: Range<usize>,
36    address_map_data: Range<usize>,
37    stack_map_data: Range<usize>,
38    exception_data: Range<usize>,
39    func_name_data: Range<usize>,
40    info_data: Range<usize>,
41    wasm_dwarf: Range<usize>,
42}
43
44impl Drop for CodeMemory {
45    fn drop(&mut self) {
46        // If there is a custom code memory handler, restore the
47        // original (non-executable) state of the memory.
48        if let Some(mem) = self.custom_code_memory.as_ref() {
49            if self.published && self.needs_executable {
50                let text = self.text();
51                mem.unpublish_executable(text.as_ptr(), text.len())
52                    .expect("Executable memory unpublish failed");
53            }
54        }
55
56        // Drop the registrations before `self.mmap` since they (implicitly) refer to it.
57        #[cfg(has_host_compiler_backend)]
58        let _ = self.unwind_registration.take();
59        #[cfg(feature = "debug-builtins")]
60        let _ = self.debug_registration.take();
61    }
62}
63
64fn _assert() {
65    fn _assert_send_sync<T: Send + Sync>() {}
66    _assert_send_sync::<CodeMemory>();
67}
68
69/// Interface implemented by an embedder to provide custom
70/// implementations of code-memory protection and execute permissions.
71pub trait CustomCodeMemory: Send + Sync {
72    /// The minimal alignment granularity for an address region that
73    /// can be made executable.
74    ///
75    /// Wasmtime does not assume the system page size for this because
76    /// custom code-memory protection can be used when all other uses
77    /// of virtual memory are disabled.
78    fn required_alignment(&self) -> usize;
79
80    /// Publish a region of memory as executable.
81    ///
82    /// This should update permissions from the default RW
83    /// (readable/writable but not executable) to RX
84    /// (readable/executable but not writable), enforcing W^X
85    /// discipline.
86    ///
87    /// If the platform requires any data/instruction coherence
88    /// action, that should be performed as part of this hook as well.
89    ///
90    /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
91    /// per `required_alignment()`.
92    fn publish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
93
94    /// Unpublish a region of memory.
95    ///
96    /// This should perform the opposite effect of `make_executable`,
97    /// switching a range of memory back from RX (readable/executable)
98    /// to RW (readable/writable). It is guaranteed that no code is
99    /// running anymore from this region.
100    ///
101    /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
102    /// per `required_alignment()`.
103    fn unpublish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>;
104}
105
106impl CodeMemory {
107    /// Creates a new `CodeMemory` by taking ownership of the provided
108    /// `MmapVec`.
109    ///
110    /// The returned `CodeMemory` manages the internal `MmapVec` and the
111    /// `publish` method is used to actually make the memory executable.
112    pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
113        let obj = ElfFile64::<Endianness>::parse(&mmap[..])
114            .map_err(obj::ObjectCrateErrorWrapper)
115            .with_context(|| "failed to parse internal compilation artifact")?;
116
117        let mut text = 0..0;
118        let mut unwind = 0..0;
119        let mut enable_branch_protection = None;
120        let mut needs_executable = true;
121        #[cfg(feature = "debug-builtins")]
122        let mut has_native_debug_info = false;
123        let mut trap_data = 0..0;
124        let mut exception_data = 0..0;
125        let mut wasm_data = 0..0;
126        let mut address_map_data = 0..0;
127        let mut stack_map_data = 0..0;
128        let mut func_name_data = 0..0;
129        let mut info_data = 0..0;
130        let mut wasm_dwarf = 0..0;
131        for section in obj.sections() {
132            let data = section.data().map_err(obj::ObjectCrateErrorWrapper)?;
133            let name = section.name().map_err(obj::ObjectCrateErrorWrapper)?;
134            let range = subslice_range(data, &mmap);
135
136            // Double-check that sections are all aligned properly.
137            if section.align() != 0 && data.len() != 0 {
138                if (data.as_ptr() as u64 - mmap.as_ptr() as u64) % section.align() != 0 {
139                    bail!(
140                        "section `{}` isn't aligned to {:#x}",
141                        section.name().unwrap_or("ERROR"),
142                        section.align()
143                    );
144                }
145            }
146
147            match name {
148                obj::ELF_WASM_BTI => match data.len() {
149                    1 => enable_branch_protection = Some(data[0] != 0),
150                    _ => bail!("invalid `{name}` section"),
151                },
152                ".text" => {
153                    text = range;
154
155                    if let SectionFlags::Elf { sh_flags } = section.flags() {
156                        if sh_flags & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
157                            needs_executable = false;
158                        }
159                    }
160
161                    // Assert that Cranelift hasn't inserted any calls that need to be
162                    // relocated. We avoid using things like Cranelift's floor/ceil/etc.
163                    // operators in the Wasm-to-Cranelift translator specifically to
164                    // avoid having to do any relocations here. This also ensures that
165                    // all builtins use the same trampoline mechanism.
166                    assert!(section.relocations().next().is_none());
167                }
168                #[cfg(has_host_compiler_backend)]
169                crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
170                obj::ELF_WASM_DATA => wasm_data = range,
171                obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
172                obj::ELF_WASMTIME_STACK_MAP => stack_map_data = range,
173                obj::ELF_WASMTIME_TRAPS => trap_data = range,
174                obj::ELF_WASMTIME_EXCEPTIONS => exception_data = range,
175                obj::ELF_NAME_DATA => func_name_data = range,
176                obj::ELF_WASMTIME_INFO => info_data = range,
177                obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
178                #[cfg(feature = "debug-builtins")]
179                ".debug_info" => has_native_debug_info = true,
180
181                _ => log::debug!("ignoring section {name}"),
182            }
183        }
184
185        // require mutability even when this is turned off
186        #[cfg(not(has_host_compiler_backend))]
187        let _ = &mut unwind;
188
189        // Ensure that the exception table is well-formed. This parser
190        // construction is cheap: it reads the header and validates
191        // ranges but nothing else. We do this only in debug-assertion
192        // builds because we otherwise require for safety that the
193        // compiled artifact is as-produced-by this version of
194        // Wasmtime, and we should always produce a correct exception
195        // table (i.e., we are not expecting untrusted data here).
196        if cfg!(debug_assertions) {
197            let _ = ExceptionTable::parse(&mmap[exception_data.clone()])?;
198        }
199
200        Ok(Self {
201            mmap,
202            #[cfg(has_host_compiler_backend)]
203            unwind_registration: None,
204            #[cfg(feature = "debug-builtins")]
205            debug_registration: None,
206            published: false,
207            enable_branch_protection: enable_branch_protection
208                .ok_or_else(|| anyhow!("missing `{}` section", obj::ELF_WASM_BTI))?,
209            needs_executable,
210            #[cfg(feature = "debug-builtins")]
211            has_native_debug_info,
212            custom_code_memory: engine.custom_code_memory().cloned(),
213            text,
214            unwind,
215            trap_data,
216            address_map_data,
217            stack_map_data,
218            exception_data,
219            func_name_data,
220            wasm_dwarf,
221            info_data,
222            wasm_data,
223        })
224    }
225
226    /// Returns a reference to the underlying `MmapVec` this memory owns.
227    #[inline]
228    pub fn mmap(&self) -> &MmapVec {
229        &self.mmap
230    }
231
232    /// Returns the contents of the text section of the ELF executable this
233    /// represents.
234    #[inline]
235    pub fn text(&self) -> &[u8] {
236        &self.mmap[self.text.clone()]
237    }
238
239    /// Returns the contents of the `ELF_WASMTIME_DWARF` section.
240    #[inline]
241    pub fn wasm_dwarf(&self) -> &[u8] {
242        &self.mmap[self.wasm_dwarf.clone()]
243    }
244
245    /// Returns the data in the `ELF_NAME_DATA` section.
246    #[inline]
247    pub fn func_name_data(&self) -> &[u8] {
248        &self.mmap[self.func_name_data.clone()]
249    }
250
251    /// Returns the concatenated list of all data associated with this wasm
252    /// module.
253    ///
254    /// This is used for initialization of memories and all data ranges stored
255    /// in a `Module` are relative to the slice returned here.
256    #[inline]
257    pub fn wasm_data(&self) -> &[u8] {
258        &self.mmap[self.wasm_data.clone()]
259    }
260
261    /// Returns the encoded address map section used to pass to
262    /// `wasmtime_environ::lookup_file_pos`.
263    #[inline]
264    pub fn address_map_data(&self) -> &[u8] {
265        &self.mmap[self.address_map_data.clone()]
266    }
267
268    /// Returns the encoded stack map section used to pass to
269    /// `wasmtime_environ::StackMap::lookup`.
270    pub fn stack_map_data(&self) -> &[u8] {
271        &self.mmap[self.stack_map_data.clone()]
272    }
273
274    /// Returns the encoded exception-tables section to pass to
275    /// `wasmtime_unwinder::ExceptionTable::parse`.
276    pub fn exception_tables(&self) -> &[u8] {
277        &self.mmap[self.exception_data.clone()]
278    }
279
280    /// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
281    /// slice if it wasn't found.
282    #[inline]
283    pub fn wasmtime_info(&self) -> &[u8] {
284        &self.mmap[self.info_data.clone()]
285    }
286
287    /// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty
288    /// slice if it wasn't found.
289    #[inline]
290    pub fn trap_data(&self) -> &[u8] {
291        &self.mmap[self.trap_data.clone()]
292    }
293
294    /// Publishes the internal ELF image to be ready for execution.
295    ///
296    /// This method can only be called once and will panic if called twice. This
297    /// will parse the ELF image from the original `MmapVec` and do everything
298    /// necessary to get it ready for execution, including:
299    ///
300    /// * Change page protections from read/write to read/execute.
301    /// * Register unwinding information with the OS
302    /// * Register this image with the debugger if native DWARF is present
303    ///
304    /// After this function executes all JIT code should be ready to execute.
305    pub fn publish(&mut self) -> Result<()> {
306        assert!(!self.published);
307        self.published = true;
308
309        if self.text().is_empty() {
310            return Ok(());
311        }
312
313        // The unsafety here comes from a few things:
314        //
315        // * We're actually updating some page protections to executable memory.
316        //
317        // * We're registering unwinding information which relies on the
318        //   correctness of the information in the first place. This applies to
319        //   both the actual unwinding tables as well as the validity of the
320        //   pointers we pass in itself.
321        unsafe {
322            // Next freeze the contents of this image by making all of the
323            // memory readonly. Nothing after this point should ever be modified
324            // so commit everything. For a compiled-in-memory image this will
325            // mean IPIs to evict writable mappings from other cores. For
326            // loaded-from-disk images this shouldn't result in IPIs so long as
327            // there weren't any relocations because nothing should have
328            // otherwise written to the image at any point either.
329            //
330            // Note that if virtual memory is disabled this is skipped because
331            // we aren't able to make it readonly, but this is just a
332            // defense-in-depth measure and isn't required for correctness.
333            #[cfg(has_virtual_memory)]
334            if self.mmap.supports_virtual_memory() {
335                self.mmap.make_readonly(0..self.mmap.len())?;
336            }
337
338            // Switch the executable portion from readonly to read/execute.
339            if self.needs_executable {
340                if !self.custom_publish()? {
341                    if !self.mmap.supports_virtual_memory() {
342                        bail!("this target requires virtual memory to be enabled");
343                    }
344                    #[cfg(has_virtual_memory)]
345                    self.mmap
346                        .make_executable(self.text.clone(), self.enable_branch_protection)
347                        .context("unable to make memory executable")?;
348                }
349            }
350
351            // With all our memory set up use the platform-specific
352            // `UnwindRegistration` implementation to inform the general
353            // runtime that there's unwinding information available for all
354            // our just-published JIT functions.
355            self.register_unwind_info()?;
356
357            #[cfg(feature = "debug-builtins")]
358            self.register_debug_image()?;
359        }
360
361        Ok(())
362    }
363
364    fn custom_publish(&mut self) -> Result<bool> {
365        if let Some(mem) = self.custom_code_memory.as_ref() {
366            let text = self.text();
367            // The text section should be aligned to
368            // `custom_code_memory.required_alignment()` due to a
369            // combination of two invariants:
370            //
371            // - MmapVec aligns its start address, even in owned-Vec mode; and
372            // - The text segment inside the ELF image will be aligned according
373            //   to the platform's requirements.
374            let text_addr = text.as_ptr() as usize;
375            assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
376
377            // The custom code memory handler will ensure the
378            // memory is executable and also handle icache
379            // coherence.
380            mem.publish_executable(text.as_ptr(), text.len())?;
381            Ok(true)
382        } else {
383            Ok(false)
384        }
385    }
386
387    unsafe fn register_unwind_info(&mut self) -> Result<()> {
388        if self.unwind.len() == 0 {
389            return Ok(());
390        }
391        #[cfg(has_host_compiler_backend)]
392        {
393            let text = self.text();
394            let unwind_info = &self.mmap[self.unwind.clone()];
395            let registration = unsafe {
396                crate::runtime::vm::UnwindRegistration::new(
397                    text.as_ptr(),
398                    unwind_info.as_ptr(),
399                    unwind_info.len(),
400                )
401                .context("failed to create unwind info registration")?
402            };
403            self.unwind_registration = Some(registration);
404            return Ok(());
405        }
406        #[cfg(not(has_host_compiler_backend))]
407        {
408            bail!("should not have unwind info for non-native backend")
409        }
410    }
411
412    #[cfg(feature = "debug-builtins")]
413    fn register_debug_image(&mut self) -> Result<()> {
414        if !self.has_native_debug_info {
415            return Ok(());
416        }
417
418        // TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful.
419        // Use the existing memory by teaching code here about relocations in DWARF sections
420        // and anything else necessary that is done in "create_gdbjit_image" right now.
421        let image = self.mmap().to_vec();
422        let text: &[u8] = self.text();
423        let bytes = crate::debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?;
424        let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes);
425        self.debug_registration = Some(reg);
426        Ok(())
427    }
428
429    /// Looks up the given offset within this module's text section and returns
430    /// the trap code associated with that instruction, if there is one.
431    pub fn lookup_trap_code(&self, text_offset: usize) -> Option<Trap> {
432        lookup_trap_code(self.trap_data(), text_offset)
433    }
434}
435
436/// Returns the range of `inner` within `outer`, such that `outer[range]` is the
437/// same as `inner`.
438///
439/// This method requires that `inner` is a sub-slice of `outer`, and if that
440/// isn't true then this method will panic.
441fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
442    if inner.len() == 0 {
443        return 0..0;
444    }
445
446    assert!(outer.as_ptr() <= inner.as_ptr());
447    assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
448
449    let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
450    start..start + inner.len()
451}