dusk_wasmtime/runtime/
code_memory.rs

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