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<Mutex<File>>),
40}
41
42pub(crate) struct DebugInfo {
43 elf_data: Option<DebugInfoSource>,
46 context: Mutex<Option<Option<addr2line::Context<DwarfReader>>>>,
55}
56
57impl DebugInfo {
58 pub(crate) fn new(elf_data: Option<DebugInfoSource>) -> Self {
59 Self {
60 elf_data,
61 context: Mutex::new(None),
62 }
63 }
64
65 pub(crate) fn with_context<T>(
69 &self,
70 f: impl FnOnce(Option<&addr2line::Context<DwarfReader>>) -> T,
71 ) -> T {
72 let mut context = self.context.lock().unwrap();
73 let context = context.get_or_insert_with(|| {
74 let elf_data = match self.elf_data.as_ref()? {
75 DebugInfoSource::Bytes(data) => data.clone(),
76 DebugInfoSource::File(file) => {
77 let guard = file.lock().unwrap();
78 let mut file = guard.try_clone().ok()?;
79 use std::io::{Read as _, Seek as _};
80 file.rewind().ok()?;
81 let mut data = Vec::new();
82 file.read_to_end(&mut data).ok()?;
83 Arc::from(data)
84 }
85 };
86 let object_file = object::File::parse(&elf_data[..]).ok()?;
87 load_dwarf_context(&object_file).ok()
88 });
89 f(context.as_ref())
90 }
91}
92
93fn load_dwarf_context(
94 object_file: &object::File<'_>,
95) -> Result<addr2line::Context<DwarfReader>, gimli::Error> {
96 let endian = if object_file.is_little_endian() {
97 gimli::RunTimeEndian::Little
98 } else {
99 gimli::RunTimeEndian::Big
100 };
101
102 let load_section = |id: gimli::SectionId| -> Result<DwarfReader, gimli::Error> {
103 let data: Vec<u8> = object_file
104 .section_by_name(id.name())
105 .and_then(|section| section.uncompressed_data().ok())
106 .map(|data| data.into_owned())
107 .unwrap_or_default();
108 Ok(gimli::EndianReader::new(Arc::from(data), endian))
109 };
110
111 let dwarf = gimli::Dwarf::load(load_section)?;
112 addr2line::Context::from_dwarf(dwarf)
113}
114
115pub static LIBCALLS_ELF: phf::Map<&'static str, LibCall> = phf::phf_map! {
121 "ceilf" => LibCall::CeilF32,
122 "ceil" => LibCall::CeilF64,
123 "floorf" => LibCall::FloorF32,
124 "floor" => LibCall::FloorF64,
125 "nearbyintf" => LibCall::NearestF32,
126 "nearbyint" => LibCall::NearestF64,
127 "sqrtf" => LibCall::SqrtF32,
128 "sqrt" => LibCall::SqrtF64,
129 "truncf" => LibCall::TruncF32,
130 "trunc" => LibCall::TruncF64,
131 "__chkstk" => LibCall::Probestack,
132 "wasmer_vm_f32_ceil" => LibCall::CeilF32,
133 "wasmer_vm_f64_ceil" => LibCall::CeilF64,
134 "wasmer_vm_f32_floor" => LibCall::FloorF32,
135 "wasmer_vm_f64_floor" => LibCall::FloorF64,
136 "wasmer_vm_f32_nearest" => LibCall::NearestF32,
137 "wasmer_vm_f64_nearest" => LibCall::NearestF64,
138 "wasmer_vm_f32_sqrt" => LibCall::SqrtF32,
139 "wasmer_vm_f64_sqrt" => LibCall::SqrtF64,
140 "wasmer_vm_f32_trunc" => LibCall::TruncF32,
141 "wasmer_vm_f64_trunc" => LibCall::TruncF64,
142 "wasmer_vm_memory32_size" => LibCall::Memory32Size,
143 "wasmer_vm_imported_memory32_size" => LibCall::ImportedMemory32Size,
144 "wasmer_vm_table_copy" => LibCall::TableCopy,
145 "wasmer_vm_table_init" => LibCall::TableInit,
146 "wasmer_vm_table_fill" => LibCall::TableFill,
147 "wasmer_vm_table_size" => LibCall::TableSize,
148 "wasmer_vm_imported_table_size" => LibCall::ImportedTableSize,
149 "wasmer_vm_table_get" => LibCall::TableGet,
150 "wasmer_vm_imported_table_get" => LibCall::ImportedTableGet,
151 "wasmer_vm_table_set" => LibCall::TableSet,
152 "wasmer_vm_imported_table_set" => LibCall::ImportedTableSet,
153 "wasmer_vm_table_grow" => LibCall::TableGrow,
154 "wasmer_vm_imported_table_grow" => LibCall::ImportedTableGrow,
155 "wasmer_vm_func_ref" => LibCall::FuncRef,
156 "wasmer_vm_elem_drop" => LibCall::ElemDrop,
157 "wasmer_vm_memory32_copy" => LibCall::Memory32Copy,
158 "wasmer_vm_memory32_fill" => LibCall::Memory32Fill,
159 "wasmer_vm_imported_memory32_fill" => LibCall::ImportedMemory32Fill,
160 "wasmer_vm_memory32_init" => LibCall::Memory32Init,
161 "wasmer_vm_data_drop" => LibCall::DataDrop,
162 "wasmer_vm_raise_trap" => LibCall::RaiseTrap,
163 "wasmer_vm_memory32_atomic_wait32" => LibCall::Memory32AtomicWait32,
164 "wasmer_vm_imported_memory32_atomic_wait32" => LibCall::ImportedMemory32AtomicWait32,
165 "wasmer_vm_memory32_atomic_wait64" => LibCall::Memory32AtomicWait64,
166 "wasmer_vm_imported_memory32_atomic_wait64" => LibCall::ImportedMemory32AtomicWait64,
167 "wasmer_vm_memory32_atomic_notify" => LibCall::Memory32AtomicNotify,
168 "wasmer_vm_imported_memory32_atomic_notify" => LibCall::ImportedMemory32AtomicNotify,
169 "wasmer_vm_throw" => LibCall::Throw,
170 "wasmer_vm_alloc_exception" => LibCall::AllocException,
171 "wasmer_vm_read_exnref" => LibCall::ReadExnRef,
172 "wasmer_vm_exception_into_exnref" => LibCall::LibunwindExceptionIntoExnRef,
173 "wasmer_eh_personality" => LibCall::EHPersonality,
174 "wasmer_eh_personality2" => LibCall::EHPersonality2,
175 "wasmer_vm_dbg_usize" => LibCall::DebugUsize,
176 "wasmer_vm_dbg_str" => LibCall::DebugStr,
177};
178
179#[cfg(unix)]
180#[derive(Debug)]
181struct ImageSegment {
182 pub(crate) mem_address: usize,
183 pub(crate) mem_size: usize,
184 pub(crate) file_address: usize,
185 pub(crate) file_size: usize,
186 pub(crate) page_size: usize,
187 pub(crate) flags: SegmentFlags,
188}
189
190#[cfg(unix)]
191impl ImageSegment {
192 fn protection(&self) -> Result<i32, String> {
193 let (read, write, exec) = match self.flags {
194 SegmentFlags::Elf { p_flags, .. } => (
195 p_flags.contains(elf::PF_R),
196 p_flags.contains(elf::PF_W),
197 p_flags.contains(elf::PF_X),
198 ),
199 _ => return Err(format!("unsupported segment flags: {:?}", self.flags)),
200 };
201
202 let mut protection = 0;
203 if read {
204 protection |= libc::PROT_READ;
205 }
206 if write {
207 protection |= libc::PROT_WRITE;
208 }
209 if exec {
210 protection |= libc::PROT_EXEC;
211 }
212 Ok(protection)
213 }
214
215 fn mem_size_page_aligned(&self) -> usize {
216 (self.mem_size + (self.mem_address - self.mem_address_page_aligned()))
217 .next_multiple_of(self.page_size)
218 }
219
220 fn mem_address_page_aligned(&self) -> usize {
221 self.mem_address & !(self.page_size - 1)
222 }
223
224 fn file_size_page_aligned(&self) -> usize {
225 (self.file_size + (self.file_address - self.file_address_page_aligned()))
226 .next_multiple_of(self.page_size)
227 }
228
229 fn file_address_page_aligned(&self) -> usize {
230 self.file_address & !(self.page_size - 1)
231 }
232}
233
234pub(crate) struct MemoryMappedBinary {
236 #[cfg(unix)]
237 base: *mut c_void,
238 #[cfg(unix)]
239 size: usize,
240
241 #[cfg(unix)]
243 unwind_registry: Option<UnwindRegistry>,
244
245 #[cfg(unix)]
248 frame_info_registration: Option<GlobalFrameInfoRegistration>,
249}
250
251unsafe impl Send for MemoryMappedBinary {}
253unsafe impl Sync for MemoryMappedBinary {}
254
255#[cfg(unix)]
256impl MemoryMappedBinary {
257 pub(crate) fn try_from_bytes<'a, R: ReadRef<'a>>(
261 object_file: &object::File<'a, R>,
262 data: &[u8],
263 ) -> Result<Self, String> {
264 Self::try_from_source(object_file, Some(data), None)
265 }
266
267 pub(crate) fn try_from_file<'a, R: ReadRef<'a>>(
269 object_file: &object::File<'a, R>,
270 file: RawFd,
271 ) -> Result<Self, String> {
272 Self::try_from_source(object_file, None, Some(file))
273 }
274
275 fn try_from_source<'a, R: ReadRef<'a>>(
276 object_file: &object::File<'a, R>,
277 data: Option<&[u8]>,
278 file: Option<RawFd>,
279 ) -> Result<Self, String> {
280 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
281 if page_size == -1 {
282 return Err("Cannot get page size".to_string());
283 }
284 let page_size = page_size as usize;
285
286 let segments = object_file
287 .segments()
288 .map(|segment| {
289 let mem_address = segment.address() as usize;
290 let mem_size = segment.size() as usize;
291 let (file_address, file_size) = segment.file_range();
292 let file_address = file_address as usize;
293 let file_size = file_size as usize;
294 ImageSegment {
295 mem_address,
296 mem_size,
297 file_address,
298 file_size,
299 page_size,
300 flags: segment.flags(),
301 }
302 })
303 .collect_vec();
304 let last_segment = segments
305 .last()
306 .ok_or("at least one segment is mandatory".to_string())?;
307 let total_memory_size =
308 last_segment.mem_address_page_aligned() + last_segment.mem_size_page_aligned();
309
310 let map = Self::new_mmap(total_memory_size)?;
313 let base = map.base();
314
315 for load_segment in segments {
317 if load_segment.file_address % page_size != load_segment.mem_address % page_size {
319 return Err(format!(
320 "Load segment file offset 0x{:x} and virtual address 0x{:x} have incompatible page alignment",
321 load_segment.file_address, load_segment.mem_address
322 ));
323 }
324
325 let protection = load_segment.protection()?;
326
327 let offset = load_segment.mem_address_page_aligned();
328 let size = load_segment.file_size_page_aligned();
329 let file_offset = load_segment.file_address_page_aligned();
330 let result = if let Some(file) = file {
331 map.map_file(offset, size, protection, file, file_offset)
332 } else {
333 map.map_copy(
334 offset,
335 size,
336 protection,
337 data.expect("byte-backed mapping requires image data"),
338 file_offset,
339 )
340 };
341 result.map_err(|error| {
342 format!(
343 "Cannot map load segment at virtual address 0x{:x}: {error}",
344 load_segment.mem_address_page_aligned()
345 )
346 })?;
347
348 if load_segment.mem_size_page_aligned() > load_segment.file_size_page_aligned() {
349 map.map_zero(
350 load_segment.mem_address_page_aligned() + load_segment.file_size_page_aligned(),
351 load_segment.mem_size_page_aligned() - load_segment.file_size_page_aligned(),
352 protection,
353 )
354 .map_err(|error| format!("Cannot map zero-fill segment tail: {error}"))?;
355 }
356 if load_segment.mem_size_page_aligned() < load_segment.file_size_page_aligned() {
357 return Err("invalid memory segment with larger file representation".to_string());
358 }
359 }
360
361 if let Some(dynamic_relocations) = object_file.dynamic_relocations() {
363 let dynamic_symbols = object_file.dynamic_symbol_table().unwrap();
364 let architecture = object_file.architecture();
365
366 for (offset, relocation) in dynamic_relocations {
367 let rel_flags = relocation.flags();
368 if matches!(
369 (architecture, rel_flags),
370 (
371 object::Architecture::X86_64,
372 object::RelocationFlags::Elf {
373 r_type: elf::R_X86_64_RELATIVE,
374 },
375 ) | (
376 object::Architecture::Aarch64,
377 object::RelocationFlags::Elf {
378 r_type: elf::R_AARCH64_RELATIVE,
379 },
380 ) | (
381 object::Architecture::Riscv64,
382 object::RelocationFlags::Elf {
383 r_type: elf::R_RISCV_RELATIVE,
384 },
385 ) | (
386 object::Architecture::LoongArch64,
387 object::RelocationFlags::Elf {
388 r_type: elf::R_LARCH_RELATIVE,
389 },
390 )
391 ) {
392 map.write_relocation(
393 offset,
394 (base as usize).wrapping_add(relocation.addend() as usize),
395 )?;
396 continue;
397 }
398
399 let object::RelocationTarget::Symbol(symbol_index) = relocation.target() else {
400 return Err("unsupported dynamic relocation target".to_string());
401 };
402 let symbol = dynamic_symbols.symbol_by_index(symbol_index).unwrap();
403 let symbol_name = symbol.name().unwrap();
404 let Some(&libcall) = LIBCALLS_ELF.get(symbol_name) else {
405 return Err(format!(
406 "unsupported dynamic relocation symbol {symbol_name}"
407 ));
408 };
409
410 let apply_absolute_relocation = || {
411 map.write_relocation(
412 offset,
413 function_pointer(libcall).wrapping_add(relocation.addend() as usize),
414 )
415 };
416 match (architecture, relocation.kind(), rel_flags) {
417 (_, object::RelocationKind::Absolute, _) => apply_absolute_relocation()?,
418 (
419 object::Architecture::X86_64,
420 object::RelocationKind::Unknown,
421 object::RelocationFlags::Elf {
422 r_type: elf::R_X86_64_GLOB_DAT | elf::R_X86_64_JUMP_SLOT,
423 },
424 ) => apply_absolute_relocation()?,
425 (
426 object::Architecture::Aarch64,
427 object::RelocationKind::Unknown,
428 object::RelocationFlags::Elf {
429 r_type: elf::R_AARCH64_GLOB_DAT | elf::R_AARCH64_JUMP_SLOT,
430 },
431 ) => apply_absolute_relocation()?,
432 (
433 object::Architecture::Riscv64,
434 object::RelocationKind::Unknown,
435 object::RelocationFlags::Elf {
436 r_type: elf::R_RISCV_64 | elf::R_RISCV_JUMP_SLOT,
437 },
438 ) => apply_absolute_relocation()?,
439 (
440 object::Architecture::LoongArch64,
441 object::RelocationKind::Unknown,
442 object::RelocationFlags::Elf {
443 r_type: elf::R_LARCH_64 | elf::R_LARCH_JUMP_SLOT,
444 },
445 ) => apply_absolute_relocation()?,
446 kind => return Err(format!("unsupported dynamic relocation kind {kind:?}")),
447 }
448 }
449 }
450
451 Ok(map)
452 }
453
454 fn write_relocation(&self, offset: u64, value: usize) -> Result<(), String> {
455 let err = || "Dynamic relocation exceeds allocated range".to_string();
456 let offset = usize::try_from(offset).map_err(|_| err())?;
457 let end = offset.checked_add(size_of::<usize>()).ok_or_else(err)?;
458 if end > self.size {
459 return Err(err());
460 }
461
462 unsafe {
464 ptr::write_unaligned(self.base.cast::<u8>().add(offset).cast::<usize>(), value);
465 }
466 Ok(())
467 }
468
469 fn new_mmap(size: usize) -> Result<Self, String> {
470 let base = unsafe {
471 libc::mmap(
472 ptr::null_mut(),
473 size,
474 libc::PROT_NONE,
475 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
476 -1,
477 0,
478 )
479 };
480 if base == libc::MAP_FAILED {
481 return Err("Cannot create a memory map for built Artifact".to_string());
482 }
483
484 Ok(Self {
485 base,
486 size,
487 unwind_registry: Some(UnwindRegistry::new()),
488 frame_info_registration: None,
489 })
490 }
491
492 pub(crate) fn base(&self) -> *mut c_void {
493 self.base
494 }
495
496 pub(crate) fn register_frame_info(&mut self, frame_info: GlobalFrameInfoRegistration) {
497 self.frame_info_registration = Some(frame_info);
498 }
499
500 #[allow(dead_code)]
506 unsafe fn as_slice(&self) -> &[u8] {
507 if self.base.is_null() || self.size == 0 {
508 return &[];
509 }
510
511 unsafe { slice::from_raw_parts(self.base.cast::<u8>(), self.size) }
512 }
513
514 #[cfg(not(target_os = "macos"))]
515 pub(crate) fn publish_eh_frame_section(
516 &mut self,
517 address: u64,
518 size: u64,
519 ) -> Result<(), String> {
520 if address.saturating_add(size) > self.size as u64 {
521 return Err("EH frame section exceeds allocated range".to_string());
522 }
523
524 let eh_frame = unsafe {
525 slice::from_raw_parts(self.base.cast::<u8>().add(address as usize), size as usize)
526 };
527 self.unwind_registry
528 .as_mut()
529 .expect("unwind registry should remain alive until MemoryMap::drop")
530 .publish_eh_frame(Some(eh_frame))
531 }
532
533 #[cfg(target_os = "macos")]
534 pub(crate) fn publish_eh_frame_section(
535 &mut self,
536 _address: u64,
537 _size: u64,
538 ) -> Result<(), String> {
539 Err("ELF artifacts are not supported on macOS".to_string())
540 }
541
542 fn map_zero(&self, offset: usize, size: usize, protection: i32) -> Result<(), String> {
545 if offset + size > self.size {
546 return Err("Segment will overwrite allocated range".to_string());
547 }
548 let result = unsafe {
549 libc::mmap(
550 self.base.add(offset),
551 size,
552 protection,
553 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
554 -1,
555 0,
556 )
557 };
558 if result == libc::MAP_FAILED {
559 return Err(std::io::Error::last_os_error().to_string());
560 }
561 Ok(())
562 }
563
564 fn map_file(
566 &self,
567 offset: usize,
568 size: usize,
569 protection: i32,
570 file: RawFd,
571 file_offset: usize,
572 ) -> Result<(), String> {
573 if offset + size > self.size {
574 return Err("Segment will overwrite allocated range".to_string());
575 }
576 let result = unsafe {
577 libc::mmap(
578 self.base.add(offset),
579 size,
580 protection,
581 libc::MAP_PRIVATE | libc::MAP_FIXED,
582 file,
583 file_offset as libc::off_t,
584 )
585 };
586 if result == libc::MAP_FAILED {
587 return Err(std::io::Error::last_os_error().to_string());
588 }
589 Ok(())
590 }
591
592 fn map_copy(
600 &self,
601 offset: usize,
602 size: usize,
603 protection: i32,
604 data: &[u8],
605 file_offset: usize,
606 ) -> Result<(), String> {
607 if offset + size > self.size {
608 return Err("Segment will overwrite allocated range".to_string());
609 }
610 let dest = unsafe { self.base.add(offset) };
611 let result = unsafe {
612 libc::mmap(
613 dest,
614 size,
615 libc::PROT_READ | libc::PROT_WRITE,
616 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
617 -1,
618 0,
619 )
620 };
621 if result == libc::MAP_FAILED {
622 return Err(std::io::Error::last_os_error().to_string());
623 }
624
625 let available = data.len().saturating_sub(file_offset).min(size);
626 unsafe {
627 ptr::copy_nonoverlapping(data.as_ptr().add(file_offset), dest as *mut u8, available);
628 }
629
630 if protection != (libc::PROT_READ | libc::PROT_WRITE)
631 && unsafe { libc::mprotect(dest, size, protection) } != 0
632 {
633 return Err(std::io::Error::last_os_error().to_string());
634 }
635 Ok(())
636 }
637}
638
639#[cfg(not(unix))]
640impl MemoryMappedBinary {
641 pub(crate) fn try_from_bytes<'a, R: ReadRef<'a>>(
642 _object_file: &object::File<'a, R>,
643 _data: &[u8],
644 ) -> Result<Self, String> {
645 Err("ELF memory mapping is only supported on Unix".to_string())
646 }
647
648 pub(crate) fn base(&self) -> *mut c_void {
649 std::ptr::null_mut()
650 }
651
652 pub(crate) fn publish_eh_frame_section(
653 &mut self,
654 _address: u64,
655 _size: u64,
656 ) -> Result<(), String> {
657 Err("ELF memory mapping is only supported on Unix".to_string())
658 }
659
660 pub(crate) fn register_frame_info(&mut self, _frame_info: GlobalFrameInfoRegistration) {}
661}
662
663#[cfg(unix)]
664impl Drop for MemoryMappedBinary {
665 fn drop(&mut self) {
666 drop(self.unwind_registry.take());
669
670 if !self.base.is_null() && self.size != 0 {
671 unsafe {
672 libc::munmap(self.base, self.size);
673 }
674 }
675 }
676}