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]
wasmtime_jit_debug_descriptor() -> *mut JITDescriptor33     fn wasmtime_jit_debug_descriptor() -> *mut JITDescriptor;
__jit_debug_register_code()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 
lock() -> LockGuard<'static>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 {
drop(&mut self)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
lock() -> LockGuard87     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
register(file: Vec<u8>) -> Self106     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
file(&self) -> &[u8]126     pub fn file(&self) -> &[u8] {
127         &self.file
128     }
129 }
130 
131 impl Drop for GdbJitImageRegistration {
drop(&mut self)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 
register_gdb_jit_image(entry: *mut JITCodeEntry)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 
unregister_gdb_jit_image(entry: *mut JITCodeEntry)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