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