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