1 //! Memory management for executable code. 2 3 use crate::prelude::*; 4 use crate::runtime::vm::{libcalls, MmapVec}; 5 use crate::Engine; 6 use alloc::sync::Arc; 7 use core::ops::Range; 8 use object::endian::Endianness; 9 use object::read::{elf::ElfFile64, Object, ObjectSection}; 10 use object::{ObjectSymbol, SectionFlags}; 11 use wasmtime_environ::{lookup_trap_code, obj, Trap}; 12 13 /// Management of executable memory within a `MmapVec` 14 /// 15 /// This type consumes ownership of a region of memory and will manage the 16 /// executable permissions of the contained JIT code as necessary. 17 pub struct CodeMemory { 18 mmap: MmapVec, 19 #[cfg(has_host_compiler_backend)] 20 unwind_registration: Option<crate::runtime::vm::UnwindRegistration>, 21 #[cfg(feature = "debug-builtins")] 22 debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>, 23 published: bool, 24 enable_branch_protection: bool, 25 needs_executable: bool, 26 #[cfg(feature = "debug-builtins")] 27 has_native_debug_info: bool, 28 custom_code_memory: Option<Arc<dyn CustomCodeMemory>>, 29 30 relocations: Vec<(usize, obj::LibCall)>, 31 32 // Ranges within `self.mmap` of where the particular sections lie. 33 text: Range<usize>, 34 unwind: Range<usize>, 35 trap_data: Range<usize>, 36 wasm_data: Range<usize>, 37 address_map_data: Range<usize>, 38 func_name_data: Range<usize>, 39 info_data: Range<usize>, 40 wasm_dwarf: Range<usize>, 41 } 42 43 impl Drop for CodeMemory { 44 fn drop(&mut self) { 45 // If there is a custom code memory handler, restore the 46 // original (non-executable) state of the memory. 47 if let Some(mem) = self.custom_code_memory.as_ref() { 48 if self.published && self.needs_executable { 49 let text = self.text(); 50 mem.unpublish_executable(text.as_ptr(), text.len()) 51 .expect("Executable memory unpublish failed"); 52 } 53 } 54 55 // Drop the registrations before `self.mmap` since they (implicitly) refer to it. 56 #[cfg(has_host_compiler_backend)] 57 let _ = self.unwind_registration.take(); 58 #[cfg(feature = "debug-builtins")] 59 let _ = self.debug_registration.take(); 60 } 61 } 62 63 fn _assert() { 64 fn _assert_send_sync<T: Send + Sync>() {} 65 _assert_send_sync::<CodeMemory>(); 66 } 67 68 /// Interface implemented by an embedder to provide custom 69 /// implementations of code-memory protection and execute permissions. 70 pub trait CustomCodeMemory: Send + Sync { 71 /// The minimal alignment granularity for an address region that 72 /// can be made executable. 73 /// 74 /// Wasmtime does not assume the system page size for this because 75 /// custom code-memory protection can be used when all other uses 76 /// of virtual memory are disabled. 77 fn required_alignment(&self) -> usize; 78 79 /// Publish a region of memory as executable. 80 /// 81 /// This should update permissions from the default RW 82 /// (readable/writable but not executable) to RX 83 /// (readable/executable but not writable), enforcing W^X 84 /// discipline. 85 /// 86 /// If the platform requires any data/instruction coherence 87 /// action, that should be performed as part of this hook as well. 88 /// 89 /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as 90 /// per `required_alignment()`. 91 fn publish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>; 92 93 /// Unpublish a region of memory. 94 /// 95 /// This should perform the opposite effect of `make_executable`, 96 /// switching a range of memory back from RX (readable/executable) 97 /// to RW (readable/writable). It is guaranteed that no code is 98 /// running anymore from this region. 99 /// 100 /// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as 101 /// per `required_alignment()`. 102 fn unpublish_executable(&self, ptr: *const u8, len: usize) -> anyhow::Result<()>; 103 } 104 105 impl CodeMemory { 106 /// Creates a new `CodeMemory` by taking ownership of the provided 107 /// `MmapVec`. 108 /// 109 /// The returned `CodeMemory` manages the internal `MmapVec` and the 110 /// `publish` method is used to actually make the memory executable. 111 pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> { 112 let obj = ElfFile64::<Endianness>::parse(&mmap[..]) 113 .map_err(obj::ObjectCrateErrorWrapper) 114 .with_context(|| "failed to parse internal compilation artifact")?; 115 116 let mut relocations = Vec::new(); 117 let mut text = 0..0; 118 let mut unwind = 0..0; 119 let mut enable_branch_protection = None; 120 let mut needs_executable = true; 121 #[cfg(feature = "debug-builtins")] 122 let mut has_native_debug_info = false; 123 let mut trap_data = 0..0; 124 let mut wasm_data = 0..0; 125 let mut address_map_data = 0..0; 126 let mut func_name_data = 0..0; 127 let mut info_data = 0..0; 128 let mut wasm_dwarf = 0..0; 129 for section in obj.sections() { 130 let data = section.data().map_err(obj::ObjectCrateErrorWrapper)?; 131 let name = section.name().map_err(obj::ObjectCrateErrorWrapper)?; 132 let range = subslice_range(data, &mmap); 133 134 // Double-check that sections are all aligned properly. 135 if section.align() != 0 && data.len() != 0 { 136 if (data.as_ptr() as u64 - mmap.as_ptr() as u64) % section.align() != 0 { 137 bail!( 138 "section `{}` isn't aligned to {:#x}", 139 section.name().unwrap_or("ERROR"), 140 section.align() 141 ); 142 } 143 } 144 145 match name { 146 obj::ELF_WASM_BTI => match data.len() { 147 1 => enable_branch_protection = Some(data[0] != 0), 148 _ => bail!("invalid `{name}` section"), 149 }, 150 ".text" => { 151 text = range; 152 153 if let SectionFlags::Elf { sh_flags } = section.flags() { 154 if sh_flags & obj::SH_WASMTIME_NOT_EXECUTED != 0 { 155 needs_executable = false; 156 } 157 } 158 159 // The text section might have relocations for things like 160 // libcalls which need to be applied, so handle those here. 161 // 162 // Note that only a small subset of possible relocations are 163 // handled. Only those required by the compiler side of 164 // things are processed. 165 for (offset, reloc) in section.relocations() { 166 assert_eq!(reloc.kind(), object::RelocationKind::Absolute); 167 assert_eq!(reloc.encoding(), object::RelocationEncoding::Generic); 168 assert_eq!(usize::from(reloc.size()), core::mem::size_of::<usize>() * 8); 169 assert_eq!(reloc.addend(), 0); 170 let sym = match reloc.target() { 171 object::RelocationTarget::Symbol(id) => id, 172 other => panic!("unknown relocation target {other:?}"), 173 }; 174 let sym = obj.symbol_by_index(sym).unwrap().name().unwrap(); 175 let libcall = obj::LibCall::from_str(sym) 176 .unwrap_or_else(|| panic!("unknown symbol relocation: {sym}")); 177 178 let offset = usize::try_from(offset).unwrap(); 179 relocations.push((offset, libcall)); 180 } 181 } 182 #[cfg(has_host_compiler_backend)] 183 crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range, 184 obj::ELF_WASM_DATA => wasm_data = range, 185 obj::ELF_WASMTIME_ADDRMAP => address_map_data = range, 186 obj::ELF_WASMTIME_TRAPS => trap_data = range, 187 obj::ELF_NAME_DATA => func_name_data = range, 188 obj::ELF_WASMTIME_INFO => info_data = range, 189 obj::ELF_WASMTIME_DWARF => wasm_dwarf = range, 190 #[cfg(feature = "debug-builtins")] 191 ".debug_info" => has_native_debug_info = true, 192 193 _ => log::debug!("ignoring section {name}"), 194 } 195 } 196 197 // require mutability even when this is turned off 198 #[cfg(not(has_host_compiler_backend))] 199 let _ = &mut unwind; 200 201 Ok(Self { 202 mmap, 203 #[cfg(has_host_compiler_backend)] 204 unwind_registration: None, 205 #[cfg(feature = "debug-builtins")] 206 debug_registration: None, 207 published: false, 208 enable_branch_protection: enable_branch_protection 209 .ok_or_else(|| anyhow!("missing `{}` section", obj::ELF_WASM_BTI))?, 210 needs_executable, 211 #[cfg(feature = "debug-builtins")] 212 has_native_debug_info, 213 custom_code_memory: engine.custom_code_memory().cloned(), 214 text, 215 unwind, 216 trap_data, 217 address_map_data, 218 func_name_data, 219 wasm_dwarf, 220 info_data, 221 wasm_data, 222 relocations, 223 }) 224 } 225 226 /// Returns a reference to the underlying `MmapVec` this memory owns. 227 #[inline] 228 pub fn mmap(&self) -> &MmapVec { 229 &self.mmap 230 } 231 232 /// Returns the contents of the text section of the ELF executable this 233 /// represents. 234 #[inline] 235 pub fn text(&self) -> &[u8] { 236 &self.mmap[self.text.clone()] 237 } 238 239 /// Returns the contents of the `ELF_WASMTIME_DWARF` section. 240 #[inline] 241 pub fn wasm_dwarf(&self) -> &[u8] { 242 &self.mmap[self.wasm_dwarf.clone()] 243 } 244 245 /// Returns the data in the `ELF_NAME_DATA` section. 246 #[inline] 247 pub fn func_name_data(&self) -> &[u8] { 248 &self.mmap[self.func_name_data.clone()] 249 } 250 251 /// Returns the concatenated list of all data associated with this wasm 252 /// module. 253 /// 254 /// This is used for initialization of memories and all data ranges stored 255 /// in a `Module` are relative to the slice returned here. 256 #[inline] 257 pub fn wasm_data(&self) -> &[u8] { 258 &self.mmap[self.wasm_data.clone()] 259 } 260 261 /// Returns the encoded address map section used to pass to 262 /// `wasmtime_environ::lookup_file_pos`. 263 #[inline] 264 pub fn address_map_data(&self) -> &[u8] { 265 &self.mmap[self.address_map_data.clone()] 266 } 267 268 /// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty 269 /// slice if it wasn't found. 270 #[inline] 271 pub fn wasmtime_info(&self) -> &[u8] { 272 &self.mmap[self.info_data.clone()] 273 } 274 275 /// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty 276 /// slice if it wasn't found. 277 #[inline] 278 pub fn trap_data(&self) -> &[u8] { 279 &self.mmap[self.trap_data.clone()] 280 } 281 282 /// Publishes the internal ELF image to be ready for execution. 283 /// 284 /// This method can only be called once and will panic if called twice. This 285 /// will parse the ELF image from the original `MmapVec` and do everything 286 /// necessary to get it ready for execution, including: 287 /// 288 /// * Change page protections from read/write to read/execute. 289 /// * Register unwinding information with the OS 290 /// * Register this image with the debugger if native DWARF is present 291 /// 292 /// After this function executes all JIT code should be ready to execute. 293 pub fn publish(&mut self) -> Result<()> { 294 assert!(!self.published); 295 self.published = true; 296 297 if self.text().is_empty() { 298 return Ok(()); 299 } 300 301 // The unsafety here comes from a few things: 302 // 303 // * We're actually updating some page protections to executable memory. 304 // 305 // * We're registering unwinding information which relies on the 306 // correctness of the information in the first place. This applies to 307 // both the actual unwinding tables as well as the validity of the 308 // pointers we pass in itself. 309 unsafe { 310 // First, if necessary, apply relocations. This can happen for 311 // things like libcalls which happen late in the lowering process 312 // that don't go through the Wasm-based libcalls layer that's 313 // indirected through the `VMContext`. Note that most modules won't 314 // have relocations, so this typically doesn't do anything. 315 self.apply_relocations()?; 316 317 // Next freeze the contents of this image by making all of the 318 // memory readonly. Nothing after this point should ever be modified 319 // so commit everything. For a compiled-in-memory image this will 320 // mean IPIs to evict writable mappings from other cores. For 321 // loaded-from-disk images this shouldn't result in IPIs so long as 322 // there weren't any relocations because nothing should have 323 // otherwise written to the image at any point either. 324 // 325 // Note that if virtual memory is disabled this is skipped because 326 // we aren't able to make it readonly, but this is just a 327 // defense-in-depth measure and isn't required for correctness. 328 #[cfg(has_virtual_memory)] 329 if self.mmap.supports_virtual_memory() { 330 self.mmap.make_readonly(0..self.mmap.len())?; 331 } 332 333 // Switch the executable portion from readonly to read/execute. 334 if self.needs_executable { 335 if !self.custom_publish()? { 336 if !self.mmap.supports_virtual_memory() { 337 bail!("this target requires virtual memory to be enabled"); 338 } 339 340 #[cfg(has_virtual_memory)] 341 { 342 let text = self.text(); 343 344 use wasmtime_jit_icache_coherence as icache_coherence; 345 346 // Clear the newly allocated code from cache if the processor requires it 347 // 348 // Do this before marking the memory as R+X, technically we should be able to do it after 349 // but there are some CPU's that have had errata about doing this with read only memory. 350 icache_coherence::clear_cache(text.as_ptr().cast(), text.len()) 351 .expect("Failed cache clear"); 352 353 self.mmap 354 .make_executable(self.text.clone(), self.enable_branch_protection) 355 .context("unable to make memory executable")?; 356 357 // Flush any in-flight instructions from the pipeline 358 icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush"); 359 } 360 } 361 } 362 363 // With all our memory set up use the platform-specific 364 // `UnwindRegistration` implementation to inform the general 365 // runtime that there's unwinding information available for all 366 // our just-published JIT functions. 367 self.register_unwind_info()?; 368 369 #[cfg(feature = "debug-builtins")] 370 self.register_debug_image()?; 371 } 372 373 Ok(()) 374 } 375 376 fn custom_publish(&mut self) -> Result<bool> { 377 if let Some(mem) = self.custom_code_memory.as_ref() { 378 let text = self.text(); 379 // The text section should be aligned to 380 // `custom_code_memory.required_alignment()` due to a 381 // combination of two invariants: 382 // 383 // - MmapVec aligns its start address, even in owned-Vec mode; and 384 // - The text segment inside the ELF image will be aligned according 385 // to the platform's requirements. 386 let text_addr = text.as_ptr() as usize; 387 assert_eq!(text_addr & (mem.required_alignment() - 1), 0); 388 389 // The custom code memory handler will ensure the 390 // memory is executable and also handle icache 391 // coherence. 392 mem.publish_executable(text.as_ptr(), text.len())?; 393 Ok(true) 394 } else { 395 Ok(false) 396 } 397 } 398 399 unsafe fn apply_relocations(&mut self) -> Result<()> { 400 if self.relocations.is_empty() { 401 return Ok(()); 402 } 403 404 if self.mmap.is_always_readonly() { 405 bail!("Unable to apply relocations to readonly MmapVec"); 406 } 407 408 for (offset, libcall) in self.relocations.iter() { 409 let offset = self.text.start + offset; 410 let libcall = match libcall { 411 obj::LibCall::FloorF32 => libcalls::relocs::floorf32 as usize, 412 obj::LibCall::FloorF64 => libcalls::relocs::floorf64 as usize, 413 obj::LibCall::NearestF32 => libcalls::relocs::nearestf32 as usize, 414 obj::LibCall::NearestF64 => libcalls::relocs::nearestf64 as usize, 415 obj::LibCall::CeilF32 => libcalls::relocs::ceilf32 as usize, 416 obj::LibCall::CeilF64 => libcalls::relocs::ceilf64 as usize, 417 obj::LibCall::TruncF32 => libcalls::relocs::truncf32 as usize, 418 obj::LibCall::TruncF64 => libcalls::relocs::truncf64 as usize, 419 obj::LibCall::FmaF32 => libcalls::relocs::fmaf32 as usize, 420 obj::LibCall::FmaF64 => libcalls::relocs::fmaf64 as usize, 421 #[cfg(target_arch = "x86_64")] 422 obj::LibCall::X86Pshufb => libcalls::relocs::x86_pshufb as usize, 423 #[cfg(not(target_arch = "x86_64"))] 424 obj::LibCall::X86Pshufb => unreachable!(), 425 }; 426 427 self.mmap 428 .as_mut_slice() 429 .as_mut_ptr() 430 .add(offset) 431 .cast::<usize>() 432 .write_unaligned(libcall); 433 } 434 Ok(()) 435 } 436 437 unsafe fn register_unwind_info(&mut self) -> Result<()> { 438 if self.unwind.len() == 0 { 439 return Ok(()); 440 } 441 #[cfg(has_host_compiler_backend)] 442 { 443 let text = self.text(); 444 let unwind_info = &self.mmap[self.unwind.clone()]; 445 let registration = crate::runtime::vm::UnwindRegistration::new( 446 text.as_ptr(), 447 unwind_info.as_ptr(), 448 unwind_info.len(), 449 ) 450 .context("failed to create unwind info registration")?; 451 self.unwind_registration = Some(registration); 452 return Ok(()); 453 } 454 #[cfg(not(has_host_compiler_backend))] 455 { 456 bail!("should not have unwind info for non-native backend") 457 } 458 } 459 460 #[cfg(feature = "debug-builtins")] 461 fn register_debug_image(&mut self) -> Result<()> { 462 if !self.has_native_debug_info { 463 return Ok(()); 464 } 465 466 // TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful. 467 // Use the existing memory by teaching code here about relocations in DWARF sections 468 // and anything else necessary that is done in "create_gdbjit_image" right now. 469 let image = self.mmap().to_vec(); 470 let text: &[u8] = self.text(); 471 let bytes = crate::debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?; 472 let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes); 473 self.debug_registration = Some(reg); 474 Ok(()) 475 } 476 477 /// Looks up the given offset within this module's text section and returns 478 /// the trap code associated with that instruction, if there is one. 479 pub fn lookup_trap_code(&self, text_offset: usize) -> Option<Trap> { 480 lookup_trap_code(self.trap_data(), text_offset) 481 } 482 } 483 484 /// Returns the range of `inner` within `outer`, such that `outer[range]` is the 485 /// same as `inner`. 486 /// 487 /// This method requires that `inner` is a sub-slice of `outer`, and if that 488 /// isn't true then this method will panic. 489 fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> { 490 if inner.len() == 0 { 491 return 0..0; 492 } 493 494 assert!(outer.as_ptr() <= inner.as_ptr()); 495 assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _)); 496 497 let start = inner.as_ptr() as usize - outer.as_ptr() as usize; 498 start..start + inner.len() 499 } 500