wasmtime/runtime/
code_memory.rs

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