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