shadow_rs/host/syscall/handler/
futex.rs1use linux_api::errno::Errno;
2use linux_api::futex::{FUTEX_BITSET_MATCH_ANY, FutexOpFlags};
3use shadow_shim_helper_rs::emulated_time::EmulatedTime;
4use shadow_shim_helper_rs::simulation_time::SimulationTime;
5use shadow_shim_helper_rs::syscall_types::ForeignPtr;
6
7use crate::core::worker::Worker;
8use crate::cshadow as c;
9use crate::host::futex_table::FutexRef;
10use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
11use crate::host::syscall::type_formatting::SyscallNonDeterministicArg;
12use crate::host::syscall::types::SyscallError;
13
14impl SyscallHandler {
15 log_syscall!(
16 futex,
17 std::ffi::c_int,
18 *const u32,
19 std::ffi::c_int,
20 u32,
21 *const std::ffi::c_void,
22 *const u32,
23 SyscallNonDeterministicArg<u32>,
24 );
25 pub fn futex(
26 ctx: &mut SyscallContext,
27 uaddr: ForeignPtr<u32>,
28 op: std::ffi::c_int,
29 val: u32,
30 utime: ForeignPtr<linux_api::time::kernel_timespec>,
31 _uaddr2: ForeignPtr<u32>,
32 val3: u32,
33 ) -> Result<std::ffi::c_int, SyscallError> {
34 let op = FutexOpFlags::from_bits_retain(op);
40
41 const POSSIBLE_OPTIONS: FutexOpFlags =
42 FutexOpFlags::FUTEX_PRIVATE_FLAG.union(FutexOpFlags::FUTEX_CLOCK_REALTIME);
43 let options = op.intersection(POSSIBLE_OPTIONS);
44 let operation = op.difference(POSSIBLE_OPTIONS);
45
46 log::trace!(
47 "futex called with addr={uaddr:p} op={op:?} (operation={operation:?} and options={options:?}) and val={val}",
48 );
49
50 match operation {
51 FutexOpFlags::FUTEX_WAIT => {
52 log::trace!("Handling FUTEX_WAIT operation {operation:?}");
53 return Self::futex_wait_helper(ctx, uaddr, val, utime, TimeoutType::Relative);
54 }
55 FutexOpFlags::FUTEX_WAKE => {
56 log::trace!("Handling FUTEX_WAKE operation {operation:?}");
57 return Ok(Self::futex_wake_helper(ctx, uaddr.cast::<()>(), val) as i32);
60 }
61 FutexOpFlags::FUTEX_WAIT_BITSET => {
62 log::trace!("Handling FUTEX_WAIT_BITSET operation {operation:?} bitset {val3:b}");
63 if val3 == FUTEX_BITSET_MATCH_ANY {
64 return Self::futex_wait_helper(ctx, uaddr, val, utime, TimeoutType::Absolute);
65 }
66 }
68 FutexOpFlags::FUTEX_WAKE_BITSET => {
69 log::trace!("Handling FUTEX_WAKE_BITSET operation {operation:?} bitset {val3:b}");
70 if val3 == FUTEX_BITSET_MATCH_ANY {
71 return Ok(Self::futex_wake_helper(ctx, uaddr.cast::<()>(), val) as i32);
74 }
75 }
77 _ => {}
78 }
79
80 log::warn!("Unhandled futex operation {operation:?}");
81 Err(Errno::ENOSYS.into())
82 }
83
84 fn futex_wake_helper(ctx: &mut SyscallContext, ptr: ForeignPtr<()>, num_wakeups: u32) -> u32 {
85 let ptr = ctx.objs.process.physical_address(ptr);
87
88 let table = ctx.objs.host.futextable_borrow();
90 let futex = table.get(ptr);
91
92 let Some(futex) = futex else {
93 log::trace!("No futex found at futex addr {ptr:p}");
94 return 0;
95 };
96
97 log::trace!("Found futex {:p} at futex addr {ptr:p}", futex.ptr());
98
99 if num_wakeups == 0 {
100 return 0;
101 }
102
103 log::trace!("Futex trying to perform {num_wakeups} wakeups");
104 let num_woken = futex.wake(num_wakeups);
105 log::trace!("Futex was able to perform {num_woken}/{num_wakeups} wakeups");
106
107 num_woken
108 }
109
110 fn futex_wait_helper(
111 ctx: &mut SyscallContext,
112 ptr: ForeignPtr<u32>,
113 expected_val: u32,
114 timeout: ForeignPtr<linux_api::time::kernel_timespec>,
115 timeout_type: TimeoutType,
116 ) -> Result<i32, SyscallError> {
117 let mem = ctx.objs.process.memory_borrow();
118
119 let timeout = if timeout.is_null() {
122 None
123 } else {
124 let tspec = mem.read(timeout)?;
125 let sim_time = SimulationTime::try_from(tspec).map_err(|_| Errno::EINVAL)?;
126 Some(sim_time)
127 };
128
129 let futex_val = mem.read(ptr)?;
133
134 log::trace!("Futex value is {futex_val}, expected value is {expected_val}");
135 if !ctx.handler.is_blocked() && futex_val != expected_val {
136 log::trace!("Futex values don't match, try again later");
137 return Err(Errno::EAGAIN.into());
138 }
139
140 let ptr = ctx.objs.process.physical_address(ptr.cast::<()>());
142
143 let mut table = ctx.objs.host.futextable_borrow_mut();
145 let futex = table.get(ptr);
146
147 if ctx.handler.is_blocked() {
148 let futex = futex.expect("syscall was blocked, but there wasn't an existing futex");
149
150 let result;
151
152 if timeout.is_some() && ctx.handler.did_listen_timeout_expire() {
154 log::trace!("Futex {ptr:p} timeout out while waiting");
156 result = Err(Errno::ETIMEDOUT);
157 } else if ctx.objs.thread.unblocked_signal_pending(
158 ctx.objs.process,
159 &ctx.objs.host.shim_shmem_lock_borrow().unwrap(),
160 ) {
161 log::trace!("Futex {ptr:p} has been interrupted by a signal");
162 result = Err(Errno::EINTR);
163 } else {
164 log::trace!("Futex {ptr:p} has been woken up");
166 result = Ok(0);
167 }
168
169 if futex.listener_count() == 0 {
171 log::trace!("Dynamically freed a futex object for futex addr {ptr:p}");
172 table.remove(ptr).expect("futex disappeared");
173 }
174
175 return result.map_err(Into::into);
176 }
177
178 let futex = match futex {
180 Some(x) => x.clone(),
181 None => {
182 log::trace!("Dynamically created a new futex object for futex addr {ptr:p}");
183
184 let futex = unsafe { c::futex_new(ptr) };
185 assert!(!futex.is_null());
186 let futex = unsafe { FutexRef::new(futex) };
187
188 table
189 .add(futex.clone())
190 .expect("new futex is already in table");
191
192 futex
193 }
194 };
195
196 log::trace!(
198 "Futex blocking for wakeup {} timeout",
199 if timeout.is_some() { "with" } else { "without" },
200 );
201 let mut rv = SyscallError::new_blocked_on_futex(futex, true);
202 if let Some(timeout) = timeout {
203 let now = Worker::current_time().unwrap();
204 let timeout = match timeout_type {
205 TimeoutType::Relative => now + timeout,
206 TimeoutType::Absolute => EmulatedTime::UNIX_EPOCH + timeout,
207 };
208
209 let timeout = std::cmp::max(timeout, now);
211
212 rv.blocked_condition().unwrap().set_timeout(Some(timeout));
213 }
214
215 Err(rv)
216 }
217
218 log_syscall!(
219 get_robust_list,
220 std::ffi::c_int,
221 std::ffi::c_int,
222 *const std::ffi::c_void,
223 *const libc::size_t,
224 );
225 pub fn get_robust_list(
226 _ctx: &mut SyscallContext,
227 _pid: std::ffi::c_int,
228 _head_ptr: ForeignPtr<ForeignPtr<linux_api::futex::robust_list_head>>,
229 _len_ptr: ForeignPtr<libc::size_t>,
230 ) -> Result<(), Errno> {
231 warn_once_then_debug!("get_robust_list was called but we don't yet support it");
232 Err(Errno::ENOSYS)
233 }
234
235 log_syscall!(
236 set_robust_list,
237 std::ffi::c_int,
238 *const std::ffi::c_void,
239 libc::size_t,
240 );
241 pub fn set_robust_list(
242 _ctx: &mut SyscallContext,
243 _head: ForeignPtr<linux_api::futex::robust_list_head>,
244 _len: libc::size_t,
245 ) -> Result<(), Errno> {
246 warn_once_then_debug!("set_robust_list was called but we don't yet support it");
247 Err(Errno::ENOSYS)
248 }
249}
250
251enum TimeoutType {
253 Relative,
255 Absolute,
257}