Skip to main content

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.contains(elf::PF_R),
193                p_flags.contains(elf::PF_W),
194                p_flags.contains(elf::PF_X),
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            let architecture = object_file.architecture();
362
363            for (offset, relocation) in dynamic_relocations {
364                let rel_flags = relocation.flags();
365                if matches!(
366                    (architecture, rel_flags),
367                    (
368                        object::Architecture::X86_64,
369                        object::RelocationFlags::Elf {
370                            r_type: elf::R_X86_64_RELATIVE,
371                        },
372                    ) | (
373                        object::Architecture::Aarch64,
374                        object::RelocationFlags::Elf {
375                            r_type: elf::R_AARCH64_RELATIVE,
376                        },
377                    ) | (
378                        object::Architecture::Riscv64,
379                        object::RelocationFlags::Elf {
380                            r_type: elf::R_RISCV_RELATIVE,
381                        },
382                    ) | (
383                        object::Architecture::LoongArch64,
384                        object::RelocationFlags::Elf {
385                            r_type: elf::R_LARCH_RELATIVE,
386                        },
387                    )
388                ) {
389                    unsafe {
390                        ptr::write_unaligned(
391                            base.add(offset as usize) as *mut usize,
392                            (base as usize).wrapping_add(relocation.addend() as usize),
393                        );
394                    }
395                    continue;
396                }
397
398                let object::RelocationTarget::Symbol(symbol_index) = relocation.target() else {
399                    return Err("unsupported dynamic relocation target".to_string());
400                };
401                let symbol = dynamic_symbols.symbol_by_index(symbol_index).unwrap();
402                let symbol_name = symbol.name().unwrap();
403                let Some(&libcall) = LIBCALLS_ELF.get(symbol_name) else {
404                    return Err(format!(
405                        "unsupported dynamic relocation symbol {symbol_name}"
406                    ));
407                };
408
409                let apply_absolute_relocation = || unsafe {
410                    ptr::write_unaligned(
411                        base.add(offset as usize) as *mut usize,
412                        function_pointer(libcall).wrapping_add(relocation.addend() as usize),
413                    );
414                };
415                match (architecture, relocation.kind(), rel_flags) {
416                    (_, object::RelocationKind::Absolute, _) => apply_absolute_relocation(),
417                    (
418                        object::Architecture::X86_64,
419                        object::RelocationKind::Unknown,
420                        object::RelocationFlags::Elf {
421                            r_type: elf::R_X86_64_GLOB_DAT | elf::R_X86_64_JUMP_SLOT,
422                        },
423                    ) => apply_absolute_relocation(),
424                    (
425                        object::Architecture::Aarch64,
426                        object::RelocationKind::Unknown,
427                        object::RelocationFlags::Elf {
428                            r_type: elf::R_AARCH64_GLOB_DAT | elf::R_AARCH64_JUMP_SLOT,
429                        },
430                    ) => apply_absolute_relocation(),
431                    (
432                        object::Architecture::Riscv64,
433                        object::RelocationKind::Unknown,
434                        object::RelocationFlags::Elf {
435                            r_type: elf::R_RISCV_64 | elf::R_RISCV_JUMP_SLOT,
436                        },
437                    ) => apply_absolute_relocation(),
438                    (
439                        object::Architecture::LoongArch64,
440                        object::RelocationKind::Unknown,
441                        object::RelocationFlags::Elf {
442                            r_type: elf::R_LARCH_64 | elf::R_LARCH_JUMP_SLOT,
443                        },
444                    ) => apply_absolute_relocation(),
445                    kind => return Err(format!("unsupported dynamic relocation kind {kind:?}")),
446                }
447            }
448        }
449
450        Ok(map)
451    }
452
453    fn new_mmap(size: usize) -> Result<Self, String> {
454        let base = unsafe {
455            libc::mmap(
456                ptr::null_mut(),
457                size,
458                libc::PROT_NONE,
459                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
460                -1,
461                0,
462            )
463        };
464        if base == libc::MAP_FAILED {
465            return Err("Cannot create a memory map for built Artifact".to_string());
466        }
467
468        Ok(Self {
469            base,
470            size,
471            unwind_registry: Some(UnwindRegistry::new()),
472            frame_info_registration: None,
473        })
474    }
475
476    pub(crate) fn base(&self) -> *mut c_void {
477        self.base
478    }
479
480    pub(crate) fn register_frame_info(&mut self, frame_info: GlobalFrameInfoRegistration) {
481        self.frame_info_registration = Some(frame_info);
482    }
483
484    /// Returns the mapped memory as a byte slice tied to the lifetime of this map.
485    ///
486    /// # Safety
487    ///
488    /// The entire mapped range must be readable for the returned slice's lifetime.
489    #[allow(dead_code)]
490    unsafe fn as_slice(&self) -> &[u8] {
491        if self.base.is_null() || self.size == 0 {
492            return &[];
493        }
494
495        unsafe { slice::from_raw_parts(self.base.cast::<u8>(), self.size) }
496    }
497
498    #[cfg(not(target_os = "macos"))]
499    pub(crate) fn publish_eh_frame_section(
500        &mut self,
501        address: u64,
502        size: u64,
503    ) -> Result<(), String> {
504        let eh_frame = unsafe {
505            slice::from_raw_parts(self.base.cast::<u8>().add(address as usize), size as usize)
506        };
507        self.unwind_registry
508            .as_mut()
509            .expect("unwind registry should remain alive until MemoryMap::drop")
510            .publish_eh_frame(Some(eh_frame))
511    }
512
513    #[cfg(target_os = "macos")]
514    pub(crate) fn publish_eh_frame_section(
515        &mut self,
516        _address: u64,
517        _size: u64,
518    ) -> Result<(), String> {
519        Err("ELF artifacts are not supported on macOS".to_string())
520    }
521
522    /// Maps an anonymous zero-filled region at `offset` with the given
523    /// protection (used for a segment's BSS tail).
524    fn map_zero(&self, offset: usize, size: usize, protection: i32) -> Result<(), String> {
525        if offset + size > self.size {
526            return Err("Segment will overwrite allocated range".to_string());
527        }
528        let result = unsafe {
529            libc::mmap(
530                self.base.add(offset),
531                size,
532                protection,
533                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
534                -1,
535                0,
536            )
537        };
538        if result == libc::MAP_FAILED {
539            return Err(std::io::Error::last_os_error().to_string());
540        }
541        Ok(())
542    }
543
544    /// Maps a region at `offset` directly from a file.
545    fn map_file(
546        &self,
547        offset: usize,
548        size: usize,
549        protection: i32,
550        file: RawFd,
551        file_offset: usize,
552    ) -> Result<(), String> {
553        if offset + size > self.size {
554            return Err("Segment will overwrite allocated range".to_string());
555        }
556        let result = unsafe {
557            libc::mmap(
558                self.base.add(offset),
559                size,
560                protection,
561                libc::MAP_PRIVATE | libc::MAP_FIXED,
562                file,
563                file_offset as libc::off_t,
564            )
565        };
566        if result == libc::MAP_FAILED {
567            return Err(std::io::Error::last_os_error().to_string());
568        }
569        Ok(())
570    }
571
572    /// Maps an anonymous region at `offset` and copies `size` bytes from
573    /// `data[file_offset..]` into it, then applies the final protection.
574    ///
575    /// Copying (rather than mapping the backing file directly) keeps this
576    /// portable: on macOS/Mach-O a file-backed `MAP_FIXED` mapping cannot be
577    /// created with executable protection, and here we don't need a real
578    /// file descriptor for the image at all.
579    fn map_copy(
580        &self,
581        offset: usize,
582        size: usize,
583        protection: i32,
584        data: &[u8],
585        file_offset: usize,
586    ) -> Result<(), String> {
587        if offset + size > self.size {
588            return Err("Segment will overwrite allocated range".to_string());
589        }
590        let dest = unsafe { self.base.add(offset) };
591        let result = unsafe {
592            libc::mmap(
593                dest,
594                size,
595                libc::PROT_READ | libc::PROT_WRITE,
596                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
597                -1,
598                0,
599            )
600        };
601        if result == libc::MAP_FAILED {
602            return Err(std::io::Error::last_os_error().to_string());
603        }
604
605        let available = data.len().saturating_sub(file_offset).min(size);
606        unsafe {
607            ptr::copy_nonoverlapping(data.as_ptr().add(file_offset), dest as *mut u8, available);
608        }
609
610        if protection != (libc::PROT_READ | libc::PROT_WRITE)
611            && unsafe { libc::mprotect(dest, size, protection) } != 0
612        {
613            return Err(std::io::Error::last_os_error().to_string());
614        }
615        Ok(())
616    }
617}
618
619#[cfg(not(unix))]
620impl MemoryMappedBinary {
621    pub(crate) fn try_from_bytes<'a, R: ReadRef<'a>>(
622        _object_file: &object::File<'a, R>,
623        _data: &[u8],
624    ) -> Result<Self, String> {
625        Err("ELF memory mapping is only supported on Unix".to_string())
626    }
627
628    pub(crate) fn base(&self) -> *mut c_void {
629        std::ptr::null_mut()
630    }
631
632    pub(crate) fn publish_eh_frame_section(
633        &mut self,
634        _address: u64,
635        _size: u64,
636    ) -> Result<(), String> {
637        Err("ELF memory mapping is only supported on Unix".to_string())
638    }
639
640    pub(crate) fn register_frame_info(&mut self, _frame_info: GlobalFrameInfoRegistration) {}
641}
642
643#[cfg(unix)]
644impl Drop for MemoryMappedBinary {
645    fn drop(&mut self) {
646        // The registered `.eh_frame` records point into this mmap, so deregister
647        // them while the mapping is still live.
648        drop(self.unwind_registry.take());
649
650        if !self.base.is_null() && self.size != 0 {
651            unsafe {
652                libc::munmap(self.base, self.size);
653            }
654        }
655    }
656}