shadow_rs/host/syscall/handler/
futex.rs

1use 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        /* rv */ std::ffi::c_int,
18        /* uaddr */ *const u32,
19        /* op */ std::ffi::c_int,
20        /* val */ u32,
21        /* utime */ *const std::ffi::c_void,
22        /* uaddr2 */ *const u32,
23        /* val3 */ 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        // TODO: currently only supports uaddr from the same virtual address space (i.e., process)
35        // Support across different address spaces requires us to compute a unique id from the
36        // hardware address (i.e., page table and offset). This is needed, e.g., when using
37        // futexes across process boundaries.
38
39        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                // TODO: Should we do better than a cast here? Maybe should add a test for this,
58                // and/or warn if it overflows?
59                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                // Other bitsets not yet handled.
67            }
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                    // TODO: Should we do better than a cast here? Maybe should add a test for this,
72                    // and/or warn if it overflows?
73                    return Ok(Self::futex_wake_helper(ctx, uaddr.cast::<()>(), val) as i32);
74                }
75                // Other bitsets not yet handled.
76            }
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        // convert the virtual ptr to a physical ptr that can uniquely identify the futex
86        let ptr = ctx.objs.process.physical_address(ptr);
87
88        // lookup the futex in the futex table
89        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        // This is a new wait operation on the futex for this thread.
120        // Check if a timeout was given in the syscall args.
121        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        // Normally, the load/compare is done atomically. Since Shadow does not run multiple
130        // threads from the same plugin at the same time, we do not use atomic ops.
131        // `man 2 futex`: blocking via a futex is an atomic compare-and-block operation
132        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        // convert the virtual ptr to a physical ptr that can uniquely identify the futex
141        let ptr = ctx.objs.process.physical_address(ptr.cast::<()>());
142
143        // check if we already have a futex
144        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            // we already blocked on wait, so this is either a timeout or wakeup
153            if timeout.is_some() && ctx.handler.did_listen_timeout_expire() {
154                // timeout while waiting for a wakeup
155                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                // proper wakeup from another thread
165                log::trace!("Futex {ptr:p} has been woken up");
166                result = Ok(0);
167            }
168
169            // dynamically clean up the futex if needed
170            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        // we'll need to block; dynamically create a futex if one does not yet exist
179        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        // now we need to block until another thread does a wake on the futex
197        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, /* restartable= */ 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            // handle if the timeout has already expired
210            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        /* rv */ std::ffi::c_int,
221        /* pid */ std::ffi::c_int,
222        /* head_ptr */ *const std::ffi::c_void,
223        /* len_ptr */ *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        /* rv */ std::ffi::c_int,
238        /* head */ *const std::ffi::c_void,
239        /* len */ 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
251/// The type of futex-wait timeout.
252enum TimeoutType {
253    /// Timeout is relative to current time.
254    Relative,
255    /// Timeout is absolute.
256    Absolute,
257}