Skip to main content

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::SectionIndex;
9use object::read::elf::SectionTable;
10use object::{
11    elf::{FileHeader64, SectionHeader64},
12    endian::Endianness,
13    read::elf::{FileHeader as _, SectionHeader as _},
14};
15use wasmtime_environ::{Trap, lookup_trap_code, obj};
16use wasmtime_unwinder::ExceptionTable;
17
18/// Management of executable memory within a `MmapVec`
19///
20/// This type consumes ownership of a region of memory and will manage the
21/// executable permissions of the contained JIT code as necessary.
22pub struct CodeMemory {
23    mmap: MmapVec,
24    #[cfg(has_host_compiler_backend)]
25    unwind_registration: Option<crate::runtime::vm::UnwindRegistration>,
26    #[cfg(feature = "debug-builtins")]
27    debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
28    published: bool,
29    registered: bool,
30    enable_branch_protection: bool,
31    needs_executable: bool,
32    #[cfg(feature = "debug-builtins")]
33    has_native_debug_info: bool,
34    custom_code_memory: Option<Arc<dyn CustomCodeMemory>>,
35
36    // Ranges within `self.mmap` of where the particular sections lie.
37    text: Range<usize>,
38    unwind: Range<usize>,
39    trap_data: Range<usize>,
40    wasm_data: Range<usize>,
41    address_map_data: Range<usize>,
42    stack_map_data: Range<usize>,
43    exception_data: Range<usize>,
44    frame_tables_data: Range<usize>,
45    func_name_data: Range<usize>,
46    info_data: Range<usize>,
47    wasm_dwarf: Range<usize>,
48}
49
50impl Drop for CodeMemory {
51    fn drop(&mut self) {
52        // If there is a custom code memory handler, restore the
53        // original (non-executable) state of the memory.
54        //
55        // We do this rather than invoking `unpublish()` because we
56        // want to skip the mprotect() if we natively own the mmap and
57        // are going to munmap soon anyway.
58        if let Some(mem) = self.custom_code_memory.as_ref() {
59            if self.published && self.needs_executable {
60                let text = self.text();
61                mem.unpublish_executable(text.as_ptr(), text.len())
62                    .expect("Executable memory unpublish failed");
63            }
64        }
65
66        // Drop the registrations before `self.mmap` since they (implicitly) refer to it.
67        #[cfg(has_host_compiler_backend)]
68        let _ = self.unwind_registration.take();
69        #[cfg(feature = "debug-builtins")]
70        let _ = self.debug_registration.take();
71    }
72}
73
74fn _assert() {
75    fn _assert_send_sync<T: Send + Sync>() {}
76    _assert_send_sync::<CodeMemory>();
77}
78
79/// Interface implemented by an embedder to provide custom
80/// implementations of code-memory protection and execute permissions.
81pub trait CustomCodeMemory: Send + Sync {
82    /// The minimal alignment granularity for an address region that
83    /// can be made executable.
84    ///
85    /// Wasmtime does not assume the system page size for this because
86    /// custom code-memory protection can be used when all other uses
87    /// of virtual memory are disabled.
88    fn required_alignment(&self) -> usize;
89
90    /// Publish a region of memory as executable.
91    ///
92    /// This should update permissions from the default RW
93    /// (readable/writable but not executable) to RX
94    /// (readable/executable but not writable), enforcing W^X
95    /// discipline.
96    ///
97    /// If the platform requires any data/instruction coherence
98    /// action, that should be performed as part of this hook as well.
99    ///
100    /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
101    /// per `required_alignment()`.
102    fn publish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
103
104    /// Unpublish a region of memory.
105    ///
106    /// This should perform the opposite effect of `make_executable`,
107    /// switching a range of memory back from RX (readable/executable)
108    /// to RW (readable/writable). It is guaranteed that no code is
109    /// running anymore from this region.
110    ///
111    /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
112    /// per `required_alignment()`.
113    fn unpublish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
114}
115
116impl CodeMemory {
117    /// Creates a new `CodeMemory` by taking ownership of the provided
118    /// `MmapVec`.
119    ///
120    /// The returned `CodeMemory` manages the internal `MmapVec` and the
121    /// `publish` method is used to actually make the memory executable.
122    pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
123        let mmap_data = &*mmap;
124        let header = FileHeader64::<Endianness>::parse(mmap_data)
125            .map_err(obj::ObjectCrateErrorWrapper)
126            .context("failed to parse precompiled artifact as an ELF")?;
127        let endian = header
128            .endian()
129            .context("failed to parse header endianness")?;
130
131        let section_headers = header
132            .section_headers(endian, mmap_data)
133            .context("failed to parse section headers")?;
134        let strings = header
135            .section_strings(endian, mmap_data, section_headers)
136            .context("failed to parse strings table")?;
137        let sections = header
138            .sections(endian, mmap_data)
139            .context("failed to parse sections table")?;
140
141        let mut text = 0..0;
142        let mut unwind = 0..0;
143        let mut enable_branch_protection = None;
144        let mut needs_executable = true;
145        #[cfg(feature = "debug-builtins")]
146        let mut has_native_debug_info = false;
147        let mut trap_data = 0..0;
148        let mut exception_data = 0..0;
149        let mut frame_tables_data = 0..0;
150        let mut wasm_data = 0..0;
151        let mut address_map_data = 0..0;
152        let mut stack_map_data = 0..0;
153        let mut func_name_data = 0..0;
154        let mut info_data = 0..0;
155        let mut wasm_dwarf = 0..0;
156        for section_header in sections.iter() {
157            let data = section_header
158                .data(endian, mmap_data)
159                .map_err(obj::ObjectCrateErrorWrapper)?;
160            let name = section_name(endian, strings, section_header)?;
161            let range = subslice_range(data, &mmap);
162
163            // Double-check that sections are all aligned properly.
164            let section_align = usize::try_from(section_header.sh_addralign(endian))?;
165            if section_align != 0 && data.len() != 0 {
166                let section_offset = data.as_ptr().addr() - mmap.as_ptr().addr();
167                ensure!(
168                    section_offset % section_align == 0,
169                    "section {name:?} isn't aligned to {section_align:#x}",
170                );
171            }
172
173            // Check that we don't have any relocations, which would make
174            // loading precompiled Wasm modules slower and also force them to
175            // get paged into memory from disk.
176            //
177            // We avoid using things like Cranelift's `floor`, `ceil`,
178            // etc... operators in the Wasm-to-CLIF translator specifically to
179            // avoid having to do any relocations here. This also ensures that
180            // all builtins use the same trampoline mechanism.
181            //
182            // We do, however, allow relocations in `.debug_*` DWARF sections.
183            if let Some(target_section) = reloc_section_target(&sections, section_header, endian)? {
184                let target_name = section_name(endian, strings, target_section)?;
185                ensure!(
186                    target_name.starts_with(".debug_"),
187                    "section {target_name:?} has unexpected relocations \
188                     (defined in section {name:?})",
189                );
190            }
191
192            match name {
193                obj::ELF_WASM_BTI => match data.len() {
194                    1 => enable_branch_protection = Some(data[0] != 0),
195                    _ => bail!("invalid {name:?} section"),
196                },
197                ".text" => {
198                    text = range;
199
200                    if section_header.sh_flags(endian) & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
201                        needs_executable = false;
202                    }
203                }
204                #[cfg(has_host_compiler_backend)]
205                crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
206                obj::ELF_WASM_DATA => wasm_data = range,
207                obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
208                obj::ELF_WASMTIME_STACK_MAP => stack_map_data = range,
209                obj::ELF_WASMTIME_TRAPS => trap_data = range,
210                obj::ELF_WASMTIME_EXCEPTIONS => exception_data = range,
211                obj::ELF_WASMTIME_FRAMES => frame_tables_data = range,
212                obj::ELF_NAME_DATA => func_name_data = range,
213                obj::ELF_WASMTIME_INFO => info_data = range,
214                obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
215
216                #[cfg(feature = "debug-builtins")]
217                ".debug_info" => has_native_debug_info = true,
218
219                // These sections are expected, but we do not need to retain any
220                // info about them.
221                "" | ".symtab" | ".strtab" | ".shstrtab" | ".xdata" | obj::ELF_WASM_ENGINE => {
222                    log::debug!("ignoring section {name:?}")
223                }
224                _ if name.starts_with(".debug_") || name.starts_with(".rela.debug_") => {
225                    log::debug!("ignoring debug section {name:?}")
226                }
227
228                _ => bail!("unexpected section {name:?} in Wasm compilation artifact"),
229            }
230        }
231
232        // Silence unused `mut` warning.
233        #[cfg(not(has_host_compiler_backend))]
234        let _ = &mut unwind;
235
236        // Ensure that the exception table is well-formed. This parser
237        // construction is cheap: it reads the header and validates
238        // ranges but nothing else. We do this only in debug-assertion
239        // builds because we otherwise require for safety that the
240        // compiled artifact is as-produced-by this version of
241        // Wasmtime, and we should always produce a correct exception
242        // table (i.e., we are not expecting untrusted data here).
243        if cfg!(debug_assertions) {
244            let _ = ExceptionTable::parse(&mmap[exception_data.clone()])?;
245        }
246
247        Ok(Self {
248            mmap,
249            #[cfg(has_host_compiler_backend)]
250            unwind_registration: None,
251            #[cfg(feature = "debug-builtins")]
252            debug_registration: None,
253            published: false,
254            registered: false,
255            enable_branch_protection: enable_branch_protection
256                .ok_or_else(|| format_err!("missing `{}` section", obj::ELF_WASM_BTI))?,
257            needs_executable,
258            #[cfg(feature = "debug-builtins")]
259            has_native_debug_info,
260            custom_code_memory: engine.custom_code_memory().cloned(),
261            text,
262            unwind,
263            trap_data,
264            address_map_data,
265            stack_map_data,
266            exception_data,
267            frame_tables_data,
268            func_name_data,
269            wasm_dwarf,
270            info_data,
271            wasm_data,
272        })
273    }
274
275    /// Returns a reference to the underlying `MmapVec` this memory owns.
276    #[inline]
277    pub fn mmap(&self) -> &MmapVec {
278        &self.mmap
279    }
280
281    /// Returns the contents of the text section of the ELF executable this
282    /// represents.
283    #[inline]
284    pub fn text(&self) -> &[u8] {
285        &self.mmap[self.text.clone()]
286    }
287
288    /// Returns the contents of the `ELF_WASMTIME_DWARF` section.
289    #[inline]
290    pub fn wasm_dwarf(&self) -> &[u8] {
291        &self.mmap[self.wasm_dwarf.clone()]
292    }
293
294    /// Returns the data in the `ELF_NAME_DATA` section.
295    #[inline]
296    pub fn func_name_data(&self) -> &[u8] {
297        &self.mmap[self.func_name_data.clone()]
298    }
299
300    /// Returns the concatenated list of all data associated with this wasm
301    /// module.
302    ///
303    /// This is used for initialization of memories and all data ranges stored
304    /// in a `Module` are relative to the slice returned here.
305    #[inline]
306    pub fn wasm_data(&self) -> &[u8] {
307        &self.mmap[self.wasm_data.clone()]
308    }
309
310    /// Returns the encoded address map section used to pass to
311    /// `wasmtime_environ::lookup_file_pos`.
312    #[inline]
313    pub fn address_map_data(&self) -> &[u8] {
314        &self.mmap[self.address_map_data.clone()]
315    }
316
317    /// Returns the encoded stack map section used to pass to
318    /// `wasmtime_environ::StackMap::lookup`.
319    pub fn stack_map_data(&self) -> &[u8] {
320        &self.mmap[self.stack_map_data.clone()]
321    }
322
323    /// Returns the encoded exception-tables section to pass to
324    /// `wasmtime_unwinder::ExceptionTable::parse`.
325    pub fn exception_tables(&self) -> &[u8] {
326        &self.mmap[self.exception_data.clone()]
327    }
328
329    /// Returns the encoded frame-tables section to pass to
330    /// `wasmtime_environ::FrameTable::parse`.
331    pub fn frame_tables(&self) -> &[u8] {
332        &self.mmap[self.frame_tables_data.clone()]
333    }
334
335    /// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
336    /// slice if it wasn't found.
337    #[inline]
338    pub fn wasmtime_info(&self) -> &[u8] {
339        &self.mmap[self.info_data.clone()]
340    }
341
342    /// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty
343    /// slice if it wasn't found.
344    #[inline]
345    pub fn trap_data(&self) -> &[u8] {
346        &self.mmap[self.trap_data.clone()]
347    }
348
349    /// Publishes the internal ELF image to be ready for execution.
350    ///
351    /// This method can only be when the image is not published (its
352    /// default state) and will panic if called when already
353    /// published. This will parse the ELF image from the original
354    /// `MmapVec` and do everything necessary to get it ready for
355    /// execution, including:
356    ///
357    /// * Change page protections from read/write to read/execute.
358    /// * Register unwinding information with the OS
359    /// * Register this image with the debugger if native DWARF is present
360    ///
361    /// After this function executes all JIT code should be ready to execute.
362    ///
363    /// The action may be reversed by calling [`Self::unpublish`], as long
364    /// as that method's safety requirements are upheld.
365    pub fn publish(&mut self) -> Result<()> {
366        assert!(!self.published);
367        self.published = true;
368
369        if self.text().is_empty() {
370            return Ok(());
371        }
372
373        // The unsafety here comes from a few things:
374        //
375        // * We're actually updating some page protections to executable memory.
376        //
377        // * We're registering unwinding information which relies on the
378        //   correctness of the information in the first place. This applies to
379        //   both the actual unwinding tables as well as the validity of the
380        //   pointers we pass in itself.
381        unsafe {
382            // Next freeze the contents of this image by making all of the
383            // memory readonly. Nothing after this point should ever be modified
384            // so commit everything. For a compiled-in-memory image this will
385            // mean IPIs to evict writable mappings from other cores. For
386            // loaded-from-disk images this shouldn't result in IPIs so long as
387            // there weren't any relocations because nothing should have
388            // otherwise written to the image at any point either.
389            //
390            // Note that if virtual memory is disabled this is skipped because
391            // we aren't able to make it readonly, but this is just a
392            // defense-in-depth measure and isn't required for correctness.
393            #[cfg(has_virtual_memory)]
394            if self.mmap.supports_virtual_memory() {
395                self.mmap.make_readonly(0..self.mmap.len())?;
396            }
397
398            // Switch the executable portion from readonly to read/execute.
399            if self.needs_executable {
400                if !self.custom_publish()? {
401                    if !self.mmap.supports_virtual_memory() {
402                        bail!("this target requires virtual memory to be enabled");
403                    }
404                    #[cfg(has_virtual_memory)]
405                    self.mmap
406                        .make_executable(self.text.clone(), self.enable_branch_protection)
407                        .context("unable to make memory executable")?;
408                }
409            }
410
411            if !self.registered {
412                // With all our memory set up use the platform-specific
413                // `UnwindRegistration` implementation to inform the general
414                // runtime that there's unwinding information available for all
415                // our just-published JIT functions.
416                self.register_unwind_info()?;
417
418                #[cfg(feature = "debug-builtins")]
419                self.register_debug_image()?;
420                self.registered = true;
421            }
422        }
423
424        Ok(())
425    }
426
427    fn custom_publish(&mut self) -> Result<bool> {
428        if let Some(mem) = self.custom_code_memory.as_ref() {
429            let text = self.text();
430            // The text section should be aligned to
431            // `custom_code_memory.required_alignment()` due to a
432            // combination of two invariants:
433            //
434            // - MmapVec aligns its start address, even in owned-Vec mode; and
435            // - The text segment inside the ELF image will be aligned according
436            //   to the platform's requirements.
437            let text_addr = text.as_ptr() as usize;
438            assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
439
440            // The custom code memory handler will ensure the
441            // memory is executable and also handle icache
442            // coherence.
443            mem.publish_executable(text.as_ptr(), text.len())?;
444            Ok(true)
445        } else {
446            Ok(false)
447        }
448    }
449
450    /// "Unpublish" code memory (transition it from executable to read/writable).
451    ///
452    /// This may be used to edit the code image, as long as the
453    /// overall size of the memory remains the same. Note the hazards
454    /// inherent in editing code that may have been executed: any
455    /// stack frames with PC still active in this code must be
456    /// suspended (e.g., called into a hostcall that is then invoking
457    /// this method, or async-yielded) and any active PC values must
458    /// point to valid instructions. Thus this is mostly useful for
459    /// patching in-place at particular sites, such as by the use of
460    /// Cranelift's `patchable_call` instruction.
461    ///
462    /// If this fails, then the memory remains executable.
463    pub fn unpublish(&mut self) -> Result<()> {
464        assert!(self.published);
465        self.published = false;
466
467        if self.text().is_empty() {
468            return Ok(());
469        }
470
471        if self.custom_unpublish()? {
472            return Ok(());
473        }
474
475        if !self.mmap.supports_virtual_memory() {
476            bail!("this target requires virtual memory to be enabled");
477        }
478
479        // SAFETY: we are guaranteed by our own safety conditions that
480        // we have exclusive access to this code and can change its
481        // permissions (removing the execute bit) without causing
482        // problems.
483        #[cfg(has_virtual_memory)]
484        unsafe {
485            self.mmap.make_readwrite(0..self.mmap.len())?;
486        }
487
488        // Note that we do *not* unregister: we expect unpublish
489        // to be used for temporary edits, so we want the
490        // registration to "stick" after the initial publish and
491        // not toggle in subsequent unpublish/publish cycles.
492
493        Ok(())
494    }
495
496    fn custom_unpublish(&mut self) -> Result<bool> {
497        if let Some(mem) = self.custom_code_memory.as_ref() {
498            let text = self.text();
499            mem.unpublish_executable(text.as_ptr(), text.len())?;
500            Ok(true)
501        } else {
502            Ok(false)
503        }
504    }
505
506    /// Return a mutable borrow to the code, suitable for editing.
507    ///
508    /// Must not be published.
509    ///
510    /// # Panics
511    ///
512    /// This method panics if the code has been published (and not
513    /// subsequently unpublished).
514    pub fn text_mut(&mut self) -> &mut [u8] {
515        assert!(!self.published);
516        // SAFETY: we assert !published, which means we either have
517        // not yet applied readonly + execute permissinos, or we have
518        // undone that and flipped back to read-write via unpublish.
519        unsafe { &mut self.mmap.as_mut_slice()[self.text.clone()] }
520    }
521
522    unsafe fn register_unwind_info(&mut self) -> Result<()> {
523        if self.unwind.len() == 0 {
524            return Ok(());
525        }
526        #[cfg(has_host_compiler_backend)]
527        {
528            let text = self.text();
529            let unwind_info = &self.mmap[self.unwind.clone()];
530            let registration = unsafe {
531                crate::runtime::vm::UnwindRegistration::new(
532                    text.as_ptr(),
533                    unwind_info.as_ptr(),
534                    unwind_info.len(),
535                )
536                .context("failed to create unwind info registration")?
537            };
538            self.unwind_registration = Some(registration);
539            return Ok(());
540        }
541        #[cfg(not(has_host_compiler_backend))]
542        {
543            bail!("should not have unwind info for non-native backend")
544        }
545    }
546
547    #[cfg(feature = "debug-builtins")]
548    fn register_debug_image(&mut self) -> Result<()> {
549        if !self.has_native_debug_info {
550            return Ok(());
551        }
552
553        // TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful.
554        // Use the existing memory by teaching code here about relocations in DWARF sections
555        // and anything else necessary that is done in "create_gdbjit_image" right now.
556        let image = self.mmap().to_vec();
557        let text: &[u8] = self.text();
558        let bytes = crate::native_debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?;
559        let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes);
560        self.debug_registration = Some(reg);
561        Ok(())
562    }
563
564    /// Looks up the given offset within this module's text section and returns
565    /// the trap code associated with that instruction, if there is one.
566    pub fn lookup_trap_code(&self, text_offset: usize) -> Option<Trap> {
567        lookup_trap_code(self.trap_data(), text_offset)
568    }
569
570    /// Get the raw address range of this CodeMemory.
571    pub(crate) fn raw_addr_range(&self) -> Range<usize> {
572        let start = self.text().as_ptr().addr();
573        let end = start + self.text().len();
574        start..end
575    }
576
577    /// Create a "deep clone": a separate CodeMemory for the same code
578    /// that can be patched or mutated independently. Also returns a
579    /// "metadata and location" handle that can be registered with the
580    /// global module registry and used for trap metadata lookups.
581    #[cfg(feature = "debug")]
582    pub(crate) fn deep_clone(self: &Arc<Self>, engine: &Engine) -> Result<CodeMemory> {
583        let mmap = self.mmap.deep_clone()?;
584        Self::new(engine, mmap)
585    }
586}
587
588fn section_name<'a>(
589    endian: Endianness,
590    strings: object::StringTable<'a>,
591    section_header: &SectionHeader64<Endianness>,
592) -> Result<&'a str> {
593    let name = section_header
594        .name(endian, strings)
595        .map_err(obj::ObjectCrateErrorWrapper)?;
596    Ok(str::from_utf8(name).context("invalid section name in Wasm compilation artifact")?)
597}
598
599fn is_reloc_section(section_header: &SectionHeader64<Endianness>, endian: Endianness) -> bool {
600    let sh_type = section_header.sh_type(endian);
601    matches!(
602        sh_type,
603        object::elf::SHT_REL | object::elf::SHT_RELA | object::elf::SHT_CREL
604    )
605}
606
607fn reloc_section_target<'a>(
608    sections: &'a SectionTable<'a, FileHeader64<Endianness>, &'a [u8]>,
609    section: &'a SectionHeader64<Endianness>,
610    endian: Endianness,
611) -> Result<Option<&'a SectionHeader64<Endianness>>> {
612    if !is_reloc_section(&section, endian) {
613        return Ok(None);
614    }
615
616    let sh_info = section.info_link(endian);
617
618    // Dynamic relocation.
619    if sh_info == SectionIndex(0) {
620        return Ok(None);
621    }
622
623    ensure!(
624        sh_info.0 < sections.len(),
625        "invalid ELF `sh_info` for relocation section",
626    );
627
628    Ok(Some(sections.section(sh_info)?))
629}
630
631/// Returns the range of `inner` within `outer`, such that `outer[range]` is the
632/// same as `inner`.
633///
634/// This method requires that `inner` is a sub-slice of `outer`, and if that
635/// isn't true then this method will panic.
636fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
637    if inner.len() == 0 {
638        return 0..0;
639    }
640
641    assert!(outer.as_ptr() <= inner.as_ptr());
642    assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
643
644    let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
645    start..start + inner.len()
646}