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