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