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