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 & 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
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
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 #[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 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 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 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 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}