wasmer_compiler/engine/
mapped_binary.rs

1use std::{
2    ffi::c_void,
3    fs::File,
4    sync::{Arc, Mutex},
5};
6#[cfg(unix)]
7use std::{os::fd::RawFd, ptr, slice};
8
9#[cfg(unix)]
10use itertools::Itertools;
11use object::{Object, ObjectSection, ReadRef};
12#[cfg(unix)]
13use object::{ObjectSegment, ObjectSymbol, ObjectSymbolTable, SegmentFlags, elf};
14use wasmer_vm::LibCall;
15#[cfg(unix)]
16use wasmer_vm::libcalls::function_pointer;
17
18use crate::GlobalFrameInfoRegistration;
19#[cfg(unix)]
20use crate::engine::unwind::UnwindRegistry;
21
22/// The `gimli` reader type used for DWARF sections loaded from an ELF image.
23///
24/// Each section's bytes are copied out of the source image into their own
25/// `Arc<[u8]>`, so the reader is independent of the lifetime of the
26/// `object::File` (or the buffer it was parsed from) used to load it.
27pub type DwarfReader = gimli::EndianArcSlice<gimli::RunTimeEndian>;
28
29/// Lazily-loaded DWARF debug info for an ELF-backed artifact.
30///
31/// Building an `addr2line::Context` parses the DWARF sections eagerly, which
32/// is wasted work for modules that are never symbolicated (e.g. no trap or
33/// backtrace ever occurs). This defers that work until the first lookup.
34#[derive(Clone)]
35pub(crate) enum DebugInfoSource {
36    Bytes(Arc<[u8]>),
37    File(Arc<File>),
38}
39
40pub(crate) struct DebugInfo {
41    /// The ELF image, kept around (or reopened) so the DWARF sections can be
42    /// loaded on first use. `None` for non-ELF artifacts.
43    elf_data: Option<DebugInfoSource>,
44    /// `None` until first accessed; `Some(None)` once loading was attempted
45    /// and failed (or there was no ELF image to load from).
46    ///
47    /// `addr2line::Context` caches parsed DWARF units behind plain
48    /// `OnceCell`s internally, so it is `Send` but not `Sync` — a `Mutex`
49    /// serializes lookups from concurrent backtraces/traps instead of
50    /// exposing a `&Context` that could be read from multiple threads at
51    /// once.
52    context: Mutex<Option<Option<addr2line::Context<DwarfReader>>>>,
53}
54
55impl DebugInfo {
56    pub(crate) fn new(elf_data: Option<DebugInfoSource>) -> Self {
57        Self {
58            elf_data,
59            context: Mutex::new(None),
60        }
61    }
62
63    /// Runs `f` with the DWARF context, building it from the ELF image on
64    /// first access. `f` receives `None` if there is no ELF image, or the
65    /// image has no (or malformed) DWARF debug info.
66    pub(crate) fn with_context<T>(
67        &self,
68        f: impl FnOnce(Option<&addr2line::Context<DwarfReader>>) -> T,
69    ) -> T {
70        let mut context = self.context.lock().unwrap();
71        let context = context.get_or_insert_with(|| {
72            let elf_data = match self.elf_data.as_ref()? {
73                DebugInfoSource::Bytes(data) => data.clone(),
74                DebugInfoSource::File(file) => {
75                    let mut file = file.try_clone().ok()?;
76                    use std::io::{Read as _, Seek as _};
77                    file.rewind().ok()?;
78                    let mut data = Vec::new();
79                    file.read_to_end(&mut data).ok()?;
80                    Arc::from(data)
81                }
82            };
83            let object_file = object::File::parse(&elf_data[..]).ok()?;
84            load_dwarf_context(&object_file).ok()
85        });
86        f(context.as_ref())
87    }
88}
89
90fn load_dwarf_context(
91    object_file: &object::File<'_>,
92) -> Result<addr2line::Context<DwarfReader>, gimli::Error> {
93    let endian = if object_file.is_little_endian() {
94        gimli::RunTimeEndian::Little
95    } else {
96        gimli::RunTimeEndian::Big
97    };
98
99    let load_section = |id: gimli::SectionId| -> Result<DwarfReader, gimli::Error> {
100        let data: Vec<u8> = object_file
101            .section_by_name(id.name())
102            .and_then(|section| section.uncompressed_data().ok())
103            .map(|data| data.into_owned())
104            .unwrap_or_default();
105        Ok(gimli::EndianReader::new(Arc::from(data), endian))
106    };
107
108    let dwarf = gimli::Dwarf::load(load_section)?;
109    addr2line::Context::from_dwarf(dwarf)
110}
111
112/// Maps an ELF dynamic-relocation symbol name to the `LibCall` it refers to.
113///
114/// Shared with `wasmer_compiler_llvm::object_file`, which resolves the same
115/// symbol names when linking an experimental artifact compilation into an object
116/// file in the first place.
117pub static LIBCALLS_ELF: phf::Map<&'static str, LibCall> = phf::phf_map! {
118    "ceilf" => LibCall::CeilF32,
119    "ceil" => LibCall::CeilF64,
120    "floorf" => LibCall::FloorF32,
121    "floor" => LibCall::FloorF64,
122    "nearbyintf" => LibCall::NearestF32,
123    "nearbyint" => LibCall::NearestF64,
124    "sqrtf" => LibCall::SqrtF32,
125    "sqrt" => LibCall::SqrtF64,
126    "truncf" => LibCall::TruncF32,
127    "trunc" => LibCall::TruncF64,
128    "__chkstk" => LibCall::Probestack,
129    "wasmer_vm_f32_ceil" => LibCall::CeilF32,
130    "wasmer_vm_f64_ceil" => LibCall::CeilF64,
131    "wasmer_vm_f32_floor" => LibCall::FloorF32,
132    "wasmer_vm_f64_floor" => LibCall::FloorF64,
133    "wasmer_vm_f32_nearest" => LibCall::NearestF32,
134    "wasmer_vm_f64_nearest" => LibCall::NearestF64,
135    "wasmer_vm_f32_sqrt" => LibCall::SqrtF32,
136    "wasmer_vm_f64_sqrt" => LibCall::SqrtF64,
137    "wasmer_vm_f32_trunc" => LibCall::TruncF32,
138    "wasmer_vm_f64_trunc" => LibCall::TruncF64,
139    "wasmer_vm_memory32_size" => LibCall::Memory32Size,
140    "wasmer_vm_imported_memory32_size" => LibCall::ImportedMemory32Size,
141    "wasmer_vm_table_copy" => LibCall::TableCopy,
142    "wasmer_vm_table_init" => LibCall::TableInit,
143    "wasmer_vm_table_fill" => LibCall::TableFill,
144    "wasmer_vm_table_size" => LibCall::TableSize,
145    "wasmer_vm_imported_table_size" => LibCall::ImportedTableSize,
146    "wasmer_vm_table_get" => LibCall::TableGet,
147    "wasmer_vm_imported_table_get" => LibCall::ImportedTableGet,
148    "wasmer_vm_table_set" => LibCall::TableSet,
149    "wasmer_vm_imported_table_set" => LibCall::ImportedTableSet,
150    "wasmer_vm_table_grow" => LibCall::TableGrow,
151    "wasmer_vm_imported_table_grow" => LibCall::ImportedTableGrow,
152    "wasmer_vm_func_ref" => LibCall::FuncRef,
153    "wasmer_vm_elem_drop" => LibCall::ElemDrop,
154    "wasmer_vm_memory32_copy" => LibCall::Memory32Copy,
155    "wasmer_vm_memory32_fill" => LibCall::Memory32Fill,
156    "wasmer_vm_imported_memory32_fill" => LibCall::ImportedMemory32Fill,
157    "wasmer_vm_memory32_init" => LibCall::Memory32Init,
158    "wasmer_vm_data_drop" => LibCall::DataDrop,
159    "wasmer_vm_raise_trap" => LibCall::RaiseTrap,
160    "wasmer_vm_memory32_atomic_wait32" => LibCall::Memory32AtomicWait32,
161    "wasmer_vm_imported_memory32_atomic_wait32" => LibCall::ImportedMemory32AtomicWait32,
162    "wasmer_vm_memory32_atomic_wait64" => LibCall::Memory32AtomicWait64,
163    "wasmer_vm_imported_memory32_atomic_wait64" => LibCall::ImportedMemory32AtomicWait64,
164    "wasmer_vm_memory32_atomic_notify" => LibCall::Memory32AtomicNotify,
165    "wasmer_vm_imported_memory32_atomic_notify" => LibCall::ImportedMemory32AtomicNotify,
166    "wasmer_vm_throw" => LibCall::Throw,
167    "wasmer_vm_alloc_exception" => LibCall::AllocException,
168    "wasmer_vm_read_exnref" => LibCall::ReadExnRef,
169    "wasmer_vm_exception_into_exnref" => LibCall::LibunwindExceptionIntoExnRef,
170    "wasmer_eh_personality" => LibCall::EHPersonality,
171    "wasmer_eh_personality2" => LibCall::EHPersonality2,
172    "wasmer_vm_dbg_usize" => LibCall::DebugUsize,
173    "wasmer_vm_dbg_str" => LibCall::DebugStr,
174};
175
176#[cfg(unix)]
177#[derive(Debug)]
178struct ImageSegment {
179    pub(crate) mem_address: usize,
180    pub(crate) mem_size: usize,
181    pub(crate) file_address: usize,
182    pub(crate) file_size: usize,
183    pub(crate) page_size: usize,
184    pub(crate) flags: SegmentFlags,
185}
186
187#[cfg(unix)]
188impl ImageSegment {
189    fn protection(&self) -> Result<i32, String> {
190        let (read, write, exec) = match self.flags {
191            SegmentFlags::Elf { p_flags } => (
192                p_flags & elf::PF_R != 0,
193                p_flags & elf::PF_W != 0,
194                p_flags & elf::PF_X != 0,
195            ),
196            _ => return Err(format!("unsupported segment flags: {:?}", self.flags)),
197        };
198
199        let mut protection = 0;
200        if read {
201            protection |= libc::PROT_READ;
202        }
203        if write {
204            protection |= libc::PROT_WRITE;
205        }
206        if exec {
207            protection |= libc::PROT_EXEC;
208        }
209        Ok(protection)
210    }
211
212    fn mem_size_page_aligned(&self) -> usize {
213        (self.mem_size + (self.mem_address - self.mem_address_page_aligned()))
214            .next_multiple_of(self.page_size)
215    }
216
217    fn mem_address_page_aligned(&self) -> usize {
218        self.mem_address & !(self.page_size - 1)
219    }
220
221    fn file_size_page_aligned(&self) -> usize {
222        (self.file_size + (self.file_address - self.file_address_page_aligned()))
223            .next_multiple_of(self.page_size)
224    }
225
226    fn file_address_page_aligned(&self) -> usize {
227        self.file_address & !(self.page_size - 1)
228    }
229}
230
231// A data structure holding a memory map of a binary in the memory.
232pub(crate) struct MemoryMappedBinary {
233    #[cfg(unix)]
234    base: *mut c_void,
235    #[cfg(unix)]
236    size: usize,
237
238    // Unwind registry associated with the binary.
239    #[cfg(unix)]
240    unwind_registry: Option<UnwindRegistry>,
241
242    // Keeps the module's frame info alive in the global registry for exactly
243    // as long as this mapping (and thus the code it points at) is alive.
244    #[cfg(unix)]
245    frame_info_registration: Option<GlobalFrameInfoRegistration>,
246}
247
248// SAFETY: memory mapped base pointer does not escape the type.
249unsafe impl Send for MemoryMappedBinary {}
250unsafe impl Sync for MemoryMappedBinary {}
251
252#[cfg(unix)]
253impl MemoryMappedBinary {
254    /// Maps `object_file`'s load segments into a freshly allocated, private
255    /// virtual address range, copying segment bytes out of the in-memory
256    /// `data` buffer (rather than mapping a file directly).
257    pub(crate) fn try_from_bytes<'a, R: ReadRef<'a>>(
258        object_file: &object::File<'a, R>,
259        data: &[u8],
260    ) -> Result<Self, String> {
261        Self::try_from_source(object_file, Some(data), None)
262    }
263
264    /// Maps an ELF image's load segments directly from an open file.
265    pub(crate) fn try_from_file<'a, R: ReadRef<'a>>(
266        object_file: &object::File<'a, R>,
267        file: RawFd,
268    ) -> Result<Self, String> {
269        Self::try_from_source(object_file, None, Some(file))
270    }
271
272    fn try_from_source<'a, R: ReadRef<'a>>(
273        object_file: &object::File<'a, R>,
274        data: Option<&[u8]>,
275        file: Option<RawFd>,
276    ) -> Result<Self, String> {
277        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
278        if page_size == -1 {
279            return Err("Cannot get page size".to_string());
280        }
281        let page_size = page_size as usize;
282
283        let segments = object_file
284            .segments()
285            .map(|segment| {
286                let mem_address = segment.address() as usize;
287                let mem_size = segment.size() as usize;
288                let (file_address, file_size) = segment.file_range();
289                let file_address = file_address as usize;
290                let file_size = file_size as usize;
291                ImageSegment {
292                    mem_address,
293                    mem_size,
294                    file_address,
295                    file_size,
296                    page_size,
297                    flags: segment.flags(),
298                }
299            })
300            .collect_vec();
301        let last_segment = segments
302            .last()
303            .ok_or("at least one segment is mandatory".to_string())?;
304        let total_memory_size =
305            last_segment.mem_address_page_aligned() + last_segment.mem_size_page_aligned();
306
307        // Create a contiguous virtual address memory map that will be populated
308        // per-partes with the individual protection flags.
309        let map = Self::new_mmap(total_memory_size)?;
310        let base = map.base();
311
312        // Mmap individual load segments
313        for load_segment in segments {
314            // The virtual offset does not need to start at a page boundary.
315            if load_segment.file_address % page_size != load_segment.mem_address % page_size {
316                return Err(format!(
317                    "Load segment file offset 0x{:x} and virtual address 0x{:x} have incompatible page alignment",
318                    load_segment.file_address, load_segment.mem_address
319                ));
320            }
321
322            let protection = load_segment.protection()?;
323
324            let offset = load_segment.mem_address_page_aligned();
325            let size = load_segment.file_size_page_aligned();
326            let file_offset = load_segment.file_address_page_aligned();
327            let result = if let Some(file) = file {
328                map.map_file(offset, size, protection, file, file_offset)
329            } else {
330                map.map_copy(
331                    offset,
332                    size,
333                    protection,
334                    data.expect("byte-backed mapping requires image data"),
335                    file_offset,
336                )
337            };
338            result.map_err(|error| {
339                format!(
340                    "Cannot map load segment at virtual address 0x{:x}: {error}",
341                    load_segment.mem_address_page_aligned()
342                )
343            })?;
344
345            if load_segment.mem_size_page_aligned() > load_segment.file_size_page_aligned() {
346                map.map_zero(
347                    load_segment.mem_address_page_aligned() + load_segment.file_size_page_aligned(),
348                    load_segment.mem_size_page_aligned() - load_segment.file_size_page_aligned(),
349                    protection,
350                )
351                .map_err(|error| format!("Cannot map zero-fill segment tail: {error}"))?;
352            }
353            if load_segment.mem_size_page_aligned() < load_segment.file_size_page_aligned() {
354                return Err("invalid memory segment with larger file representation".to_string());
355            }
356        }
357
358        // Apply dynamic relocations for the libcalls
359        if let Some(dynamic_relocations) = object_file.dynamic_relocations() {
360            let dynamic_symbols = object_file.dynamic_symbol_table().unwrap();
361
362            for (offset, relocation) in dynamic_relocations {
363                let rel_flags = relocation.flags();
364                if matches!(
365                    rel_flags,
366                    object::RelocationFlags::Elf {
367                        r_type: elf::R_X86_64_RELATIVE,
368                    }
369                ) {
370                    unsafe {
371                        ptr::write_unaligned(
372                            base.add(offset as usize) as *mut usize,
373                            (base as usize).wrapping_add(relocation.addend() as usize),
374                        );
375                    }
376                    continue;
377                }
378
379                let object::RelocationTarget::Symbol(symbol_index) = relocation.target() else {
380                    return Err("unsupported dynamic relocation target".to_string());
381                };
382                let symbol = dynamic_symbols.symbol_by_index(symbol_index).unwrap();
383                let symbol_name = symbol.name().unwrap();
384                let Some(&libcall) = LIBCALLS_ELF.get(symbol_name) else {
385                    return Err(format!(
386                        "unsupported dynamic relocation symbol {symbol_name}"
387                    ));
388                };
389
390                let apply_absolute_relocation = || unsafe {
391                    ptr::write_unaligned(
392                        base.add(offset as usize) as *mut usize,
393                        function_pointer(libcall).wrapping_add(relocation.addend() as usize),
394                    );
395                };
396                match (relocation.kind(), rel_flags) {
397                    (object::RelocationKind::Absolute, _) => apply_absolute_relocation(),
398                    (
399                        object::RelocationKind::Unknown,
400                        object::RelocationFlags::Elf {
401                            r_type: elf::R_X86_64_GLOB_DAT,
402                        },
403                    ) => apply_absolute_relocation(),
404                    (
405                        object::RelocationKind::Unknown,
406                        object::RelocationFlags::Elf {
407                            r_type: elf::R_X86_64_JUMP_SLOT,
408                        },
409                    ) => apply_absolute_relocation(),
410                    kind => return Err(format!("unsupported dynamic relocation kind {kind:?}")),
411                }
412            }
413        }
414
415        Ok(map)
416    }
417
418    fn new_mmap(size: usize) -> Result<Self, String> {
419        let base = unsafe {
420            libc::mmap(
421                ptr::null_mut(),
422                size,
423                libc::PROT_NONE,
424                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
425                -1,
426                0,
427            )
428        };
429        if base == libc::MAP_FAILED {
430            return Err("Cannot create a memory map for built Artifact".to_string());
431        }
432
433        Ok(Self {
434            base,
435            size,
436            unwind_registry: Some(UnwindRegistry::new()),
437            frame_info_registration: None,
438        })
439    }
440
441    pub(crate) fn base(&self) -> *mut c_void {
442        self.base
443    }
444
445    pub(crate) fn register_frame_info(&mut self, frame_info: GlobalFrameInfoRegistration) {
446        self.frame_info_registration = Some(frame_info);
447    }
448
449    /// Returns the mapped memory as a byte slice tied to the lifetime of this map.
450    ///
451    /// # Safety
452    ///
453    /// The entire mapped range must be readable for the returned slice's lifetime.
454    #[allow(dead_code)]
455    unsafe fn as_slice(&self) -> &[u8] {
456        if self.base.is_null() || self.size == 0 {
457            return &[];
458        }
459
460        unsafe { slice::from_raw_parts(self.base.cast::<u8>(), self.size) }
461    }
462
463    #[cfg(not(target_os = "macos"))]
464    pub(crate) fn publish_eh_frame_section(
465        &mut self,
466        address: u64,
467        size: u64,
468    ) -> Result<(), String> {
469        let eh_frame = unsafe {
470            slice::from_raw_parts(self.base.cast::<u8>().add(address as usize), size as usize)
471        };
472        self.unwind_registry
473            .as_mut()
474            .expect("unwind registry should remain alive until MemoryMap::drop")
475            .publish_eh_frame(Some(eh_frame))
476    }
477
478    #[cfg(target_os = "macos")]
479    pub(crate) fn publish_eh_frame_section(
480        &mut self,
481        _address: u64,
482        _size: u64,
483    ) -> Result<(), String> {
484        Err("ELF artifacts are not supported on macOS".to_string())
485    }
486
487    /// Maps an anonymous zero-filled region at `offset` with the given
488    /// protection (used for a segment's BSS tail).
489    fn map_zero(&self, offset: usize, size: usize, protection: i32) -> Result<(), String> {
490        if offset + size > self.size {
491            return Err("Segment will overwrite allocated range".to_string());
492        }
493        let result = unsafe {
494            libc::mmap(
495                self.base.add(offset),
496                size,
497                protection,
498                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
499                -1,
500                0,
501            )
502        };
503        if result == libc::MAP_FAILED {
504            return Err(std::io::Error::last_os_error().to_string());
505        }
506        Ok(())
507    }
508
509    /// Maps a region at `offset` directly from a file.
510    fn map_file(
511        &self,
512        offset: usize,
513        size: usize,
514        protection: i32,
515        file: RawFd,
516        file_offset: usize,
517    ) -> Result<(), String> {
518        if offset + size > self.size {
519            return Err("Segment will overwrite allocated range".to_string());
520        }
521        let result = unsafe {
522            libc::mmap(
523                self.base.add(offset),
524                size,
525                protection,
526                libc::MAP_PRIVATE | libc::MAP_FIXED,
527                file,
528                file_offset as libc::off_t,
529            )
530        };
531        if result == libc::MAP_FAILED {
532            return Err(std::io::Error::last_os_error().to_string());
533        }
534        Ok(())
535    }
536
537    /// Maps an anonymous region at `offset` and copies `size` bytes from
538    /// `data[file_offset..]` into it, then applies the final protection.
539    ///
540    /// Copying (rather than mapping the backing file directly) keeps this
541    /// portable: on macOS/Mach-O a file-backed `MAP_FIXED` mapping cannot be
542    /// created with executable protection, and here we don't need a real
543    /// file descriptor for the image at all.
544    fn map_copy(
545        &self,
546        offset: usize,
547        size: usize,
548        protection: i32,
549        data: &[u8],
550        file_offset: usize,
551    ) -> Result<(), String> {
552        if offset + size > self.size {
553            return Err("Segment will overwrite allocated range".to_string());
554        }
555        let dest = unsafe { self.base.add(offset) };
556        let result = unsafe {
557            libc::mmap(
558                dest,
559                size,
560                libc::PROT_READ | libc::PROT_WRITE,
561                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
562                -1,
563                0,
564            )
565        };
566        if result == libc::MAP_FAILED {
567            return Err(std::io::Error::last_os_error().to_string());
568        }
569
570        let available = data.len().saturating_sub(file_offset).min(size);
571        unsafe {
572            ptr::copy_nonoverlapping(data.as_ptr().add(file_offset), dest as *mut u8, available);
573        }
574
575        if protection != (libc::PROT_READ | libc::PROT_WRITE)
576            && unsafe { libc::mprotect(dest, size, protection) } != 0
577        {
578            return Err(std::io::Error::last_os_error().to_string());
579        }
580        Ok(())
581    }
582}
583
584#[cfg(not(unix))]
585impl MemoryMappedBinary {
586    pub(crate) fn try_from_bytes<'a, R: ReadRef<'a>>(
587        _object_file: &object::File<'a, R>,
588        _data: &[u8],
589    ) -> Result<Self, String> {
590        Err("ELF memory mapping is only supported on Unix".to_string())
591    }
592
593    pub(crate) fn base(&self) -> *mut c_void {
594        std::ptr::null_mut()
595    }
596
597    pub(crate) fn publish_eh_frame_section(
598        &mut self,
599        _address: u64,
600        _size: u64,
601    ) -> Result<(), String> {
602        Err("ELF memory mapping is only supported on Unix".to_string())
603    }
604
605    pub(crate) fn register_frame_info(&mut self, _frame_info: GlobalFrameInfoRegistration) {}
606}
607
608#[cfg(unix)]
609impl Drop for MemoryMappedBinary {
610    fn drop(&mut self) {
611        // The registered `.eh_frame` records point into this mmap, so deregister
612        // them while the mapping is still live.
613        drop(self.unwind_registry.take());
614
615        if !self.base.is_null() && self.size != 0 {
616            unsafe {
617                libc::munmap(self.base, self.size);
618            }
619        }
620    }
621}