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
22pub type DwarfReader = gimli::EndianArcSlice<gimli::RunTimeEndian>;
28
29#[derive(Clone)]
35pub(crate) enum DebugInfoSource {
36 Bytes(Arc<[u8]>),
37 File(Arc<File>),
38}
39
40pub(crate) struct DebugInfo {
41 elf_data: Option<DebugInfoSource>,
44 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 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
112pub 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
231pub(crate) struct MemoryMappedBinary {
233 #[cfg(unix)]
234 base: *mut c_void,
235 #[cfg(unix)]
236 size: usize,
237
238 #[cfg(unix)]
240 unwind_registry: Option<UnwindRegistry>,
241
242 #[cfg(unix)]
245 frame_info_registration: Option<GlobalFrameInfoRegistration>,
246}
247
248unsafe impl Send for MemoryMappedBinary {}
250unsafe impl Sync for MemoryMappedBinary {}
251
252#[cfg(unix)]
253impl MemoryMappedBinary {
254 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 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 let map = Self::new_mmap(total_memory_size)?;
310 let base = map.base();
311
312 for load_segment in segments {
314 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 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 #[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 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 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 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 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}