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