1 //! The GDB's JIT compilation interface. The low level module that exposes 2 //! the __jit_debug_register_code() and __jit_debug_descriptor to register 3 //! or unregister generated object images with debuggers. 4 5 extern crate alloc; 6 7 use alloc::{boxed::Box, vec::Vec}; 8 use core::{pin::Pin, ptr}; 9 use wasmtime_versioned_export_macros::versioned_link; 10 11 #[repr(C)] 12 struct JITCodeEntry { 13 next_entry: *mut JITCodeEntry, 14 prev_entry: *mut JITCodeEntry, 15 symfile_addr: *const u8, 16 symfile_size: u64, 17 } 18 19 const JIT_NOACTION: u32 = 0; 20 const JIT_REGISTER_FN: u32 = 1; 21 const JIT_UNREGISTER_FN: u32 = 2; 22 23 #[repr(C)] 24 struct JITDescriptor { 25 version: u32, 26 action_flag: u32, 27 relevant_entry: *mut JITCodeEntry, 28 first_entry: *mut JITCodeEntry, 29 } 30 31 unsafe extern "C" { 32 #[versioned_link] 33 fn wasmtime_jit_debug_descriptor() -> *mut JITDescriptor; 34 fn __jit_debug_register_code(); 35 } 36 37 #[cfg(feature = "std")] 38 mod gdb_registration { 39 use std::sync::{Mutex, MutexGuard}; 40 41 /// The process controls access to the __jit_debug_descriptor by itself -- 42 /// the GDB/LLDB accesses this structure and its data at the process startup 43 /// and when paused in __jit_debug_register_code. 44 /// 45 /// The GDB_REGISTRATION lock is needed for GdbJitImageRegistration to protect 46 /// access to the __jit_debug_descriptor within this process. 47 pub static GDB_REGISTRATION: Mutex<()> = Mutex::new(()); 48 49 /// The lock guard for the GDB registration lock. 50 #[expect( 51 dead_code, 52 reason = "field used to hold the lock until the end of the scope" 53 )] 54 pub struct LockGuard<'a>(MutexGuard<'a, ()>); 55 56 pub fn lock() -> LockGuard<'static> { 57 LockGuard(GDB_REGISTRATION.lock().unwrap()) 58 } 59 } 60 61 /// For no_std there's no access to synchronization primitives so a primitive 62 /// fallback for now is to panic-on-contention which is, in theory, rare to come 63 /// up as threads are rarer in no_std mode too. 64 /// 65 /// This provides the guarantee that the debugger will see consistent state at 66 /// least, but if this panic is hit in practice it'll require some sort of 67 /// no_std synchronization mechanism one way or another. 68 #[cfg(not(feature = "std"))] 69 mod gdb_registration { 70 use core::sync::atomic::{AtomicBool, Ordering}; 71 72 /// Whether or not a lock is held or not. 73 pub static GDB_REGISTRATION: AtomicBool = AtomicBool::new(false); 74 75 /// The lock guard for the GDB registration lock. 76 pub struct LockGuard; 77 78 /// When the `LockGuard` is dropped, it releases the lock by setting 79 /// `GDB_REGISTRATION` to false. 80 impl Drop for LockGuard { 81 fn drop(&mut self) { 82 GDB_REGISTRATION.store(false, Ordering::Release); 83 } 84 } 85 86 /// Locks the GDB registration lock. If the lock is already held, it panics 87 pub fn lock() -> LockGuard { 88 if GDB_REGISTRATION 89 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) 90 .is_err() 91 { 92 panic!("GDB JIT registration lock contention detected in no_std mode"); 93 } 94 LockGuard 95 } 96 } 97 98 /// Registration for JIT image 99 pub struct GdbJitImageRegistration { 100 entry: Pin<Box<JITCodeEntry>>, 101 file: Pin<Box<[u8]>>, 102 } 103 104 impl GdbJitImageRegistration { 105 /// Registers JIT image using __jit_debug_register_code 106 pub fn register(file: Vec<u8>) -> Self { 107 let file = Pin::new(file.into_boxed_slice()); 108 109 // Create a code entry for the file, which gives the start and size 110 // of the symbol file. 111 let mut entry = Pin::new(Box::new(JITCodeEntry { 112 next_entry: ptr::null_mut(), 113 prev_entry: ptr::null_mut(), 114 symfile_addr: file.as_ptr(), 115 symfile_size: file.len() as u64, 116 })); 117 118 unsafe { 119 register_gdb_jit_image(&mut *entry); 120 } 121 122 Self { entry, file } 123 } 124 125 /// JIT image used in registration 126 pub fn file(&self) -> &[u8] { 127 &self.file 128 } 129 } 130 131 impl Drop for GdbJitImageRegistration { 132 fn drop(&mut self) { 133 unsafe { 134 unregister_gdb_jit_image(&mut *self.entry); 135 } 136 } 137 } 138 139 unsafe impl Send for GdbJitImageRegistration {} 140 unsafe impl Sync for GdbJitImageRegistration {} 141 142 unsafe fn register_gdb_jit_image(entry: *mut JITCodeEntry) { 143 unsafe { 144 let _lock = gdb_registration::lock(); 145 let desc = &mut *wasmtime_jit_debug_descriptor(); 146 147 // Add it to the linked list in the JIT descriptor. 148 (*entry).next_entry = desc.first_entry; 149 if !desc.first_entry.is_null() { 150 (*desc.first_entry).prev_entry = entry; 151 } 152 desc.first_entry = entry; 153 // Point the relevant_entry field of the descriptor at the entry. 154 desc.relevant_entry = entry; 155 // Set action_flag to JIT_REGISTER and call __jit_debug_register_code. 156 desc.action_flag = JIT_REGISTER_FN; 157 __jit_debug_register_code(); 158 159 desc.action_flag = JIT_NOACTION; 160 desc.relevant_entry = ptr::null_mut(); 161 } 162 } 163 164 unsafe fn unregister_gdb_jit_image(entry: *mut JITCodeEntry) { 165 unsafe { 166 let _lock = gdb_registration::lock(); 167 let desc = &mut *wasmtime_jit_debug_descriptor(); 168 169 // Remove the code entry corresponding to the code from the linked list. 170 if !(*entry).prev_entry.is_null() { 171 (*(*entry).prev_entry).next_entry = (*entry).next_entry; 172 } else { 173 desc.first_entry = (*entry).next_entry; 174 } 175 if !(*entry).next_entry.is_null() { 176 (*(*entry).next_entry).prev_entry = (*entry).prev_entry; 177 } 178 // Point the relevant_entry field of the descriptor at the code entry. 179 desc.relevant_entry = entry; 180 // Set action_flag to JIT_UNREGISTER and call __jit_debug_register_code. 181 desc.action_flag = JIT_UNREGISTER_FN; 182 __jit_debug_register_code(); 183 184 desc.action_flag = JIT_NOACTION; 185 desc.relevant_entry = ptr::null_mut(); 186 } 187 } 188