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