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