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