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