Skip to main content

starnix_core/execution/
executor.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::execution::loop_entry::enter_syscall_loop;
6use crate::ptrace::{PtraceCoreState, ptrace_attach_from_state};
7use crate::task::{CurrentTask, DelayedReleaser, ExitStatus, TaskBuilder};
8use anyhow::Error;
9use starnix_logging::{log_error, log_warn};
10use starnix_sync::{LockBefore, Locked, TaskRelease, Unlocked};
11use starnix_types::ownership::WeakRef;
12use starnix_uapi::errors::Errno;
13use starnix_uapi::{errno, error};
14use std::os::unix::thread::JoinHandleExt;
15use std::sync::Arc;
16use std::sync::mpsc::sync_channel;
17
18pub fn execute_task_with_prerun_result<L, F, R, G>(
19    locked: &mut Locked<L>,
20    task_builder: TaskBuilder,
21    pre_run: F,
22    task_complete: G,
23    ptrace_state: Option<PtraceCoreState>,
24) -> Result<R, Errno>
25where
26    L: LockBefore<TaskRelease>,
27    F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> Result<R, Errno> + Send + Sync + 'static,
28    R: Send + Sync + 'static,
29    G: FnOnce(Result<ExitStatus, Error>) + Send + Sync + 'static,
30{
31    let (sender, receiver) = sync_channel::<Result<R, Errno>>(1);
32    execute_task(
33        locked,
34        task_builder,
35        move |current_task, locked| match pre_run(current_task, locked) {
36            Err(errno) => {
37                let _ = sender.send(Err(errno.clone()));
38                Err(errno)
39            }
40            Ok(value) => sender.send(Ok(value)).map_err(|error| {
41                log_error!("Unable to send `pre_run` result: {error:?}");
42                errno!(EINVAL)
43            }),
44        },
45        task_complete,
46        ptrace_state,
47    )?;
48    receiver.recv().map_err(|e| {
49        log_error!("Unable to retrieve result from `pre_run`: {e:?}");
50        errno!(EINVAL)
51    })?
52}
53
54pub fn execute_task<L, F, G>(
55    locked: &mut Locked<L>,
56    task_builder: TaskBuilder,
57    pre_run: F,
58    task_complete: G,
59    ptrace_state: Option<PtraceCoreState>,
60) -> Result<(), Errno>
61where
62    L: LockBefore<TaskRelease>,
63    F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> Result<(), Errno> + Send + Sync + 'static,
64    G: FnOnce(Result<ExitStatus, Error>) + Send + Sync + 'static,
65{
66    // Set the process handle to the new task's process, so the new thread is spawned in that
67    // process.
68    let process_handle = task_builder.task.thread_group().process.raw_handle();
69    // SAFETY: thrd_set_zx_process is a safe function that only sets the process handle for the
70    // current thread. Which handle is used here is only for diagnostics.
71    let old_process_handle = unsafe { thrd_set_zx_process(process_handle) };
72    scopeguard::defer! {
73        // SAFETY: thrd_set_zx_process is a safe function that only sets the process handle for the
74        // current thread. Which handle is used here is only for diagnostics.
75        unsafe {
76            thrd_set_zx_process(old_process_handle);
77        };
78    };
79
80    let weak_task = WeakRef::from(&task_builder.task);
81    let ref_task = weak_task.upgrade().unwrap();
82    if let Some(ptrace_state) = ptrace_state {
83        let _ = ptrace_attach_from_state(
84            locked.cast_locked::<TaskRelease>(),
85            &task_builder.task,
86            ptrace_state,
87        );
88    }
89
90    // Hold a lock on the task's thread slot until we have a chance to initialize it.
91    let live_task = ref_task.live().unwrap();
92    let mut task_thread_guard = live_task.thread.write();
93
94    // Spawn the process' thread. Note, this closure ends up executing in the process referred to by
95    // `process_handle`.
96    let (sender, receiver) = sync_channel::<TaskBuilder>(1);
97    let result = std::thread::Builder::new().name("user-thread".to_string()).spawn(move || {
98        // It's safe to create a new lock context since we are on a new thread.
99        #[allow(
100            clippy::undocumented_unsafe_blocks,
101            reason = "Force documented unsafe blocks in Starnix"
102        )]
103        let locked = unsafe { Unlocked::new() };
104
105        // Note, cross-process shared resources allocated in this function that aren't freed by the
106        // Zircon kernel upon thread and/or process termination (like mappings in the shared region)
107        // should be freed using the delayed finalizer mechanism and Task drop.
108        let mut current_task: CurrentTask = receiver
109            .recv()
110            .expect("caller should always send task builder before disconnecting")
111            .into();
112
113        // We don't need the receiver anymore. If we don't drop the receiver now, we'll keep it
114        // allocated for the lifetime of the thread.
115        std::mem::drop(receiver);
116
117        let pre_run_result = { pre_run(locked, &mut current_task) };
118        if pre_run_result.is_err() {
119            // Only log if the pre run didn't exit the task. Otherwise, consider this is expected
120            // by the caller.
121            if current_task.exit_status().is_none() {
122                log_error!("Pre run failed from {pre_run_result:?}. The task will not be run.");
123            }
124
125            // Drop the task_complete callback to ensure that the closure isn't holding any
126            // releasables.
127            std::mem::drop(task_complete);
128        } else {
129            let exit_status = enter_syscall_loop(locked, &mut current_task);
130            current_task.write().set_exit_status(exit_status.clone());
131            task_complete(Ok(exit_status));
132        }
133
134        // `release` must be called as the absolute last action on this thread to ensure that
135        // any deferred release are done before it.
136        current_task.release(locked);
137
138        // Ensure that no releasables are registered after this point as we unwind the stack.
139        DelayedReleaser::finalize();
140    });
141    let join_handle = match result {
142        Ok(handle) => handle,
143        Err(e) => {
144            task_builder.release(locked);
145            match e.kind() {
146                std::io::ErrorKind::WouldBlock => return error!(EAGAIN),
147                other => panic!("unexpected error on thread spawn: {other}"),
148            }
149        }
150    };
151
152    // Update the thread and task information before sending the task_builder to the spawned thread.
153    // This will make sure the mapping between linux tid and fuchsia koid is set before trace events
154    // are emitted from the linux code.
155
156    // Set the task's thread handle
157    let pthread = join_handle.as_pthread_t();
158    #[allow(
159        clippy::undocumented_unsafe_blocks,
160        reason = "Force documented unsafe blocks in Starnix"
161    )]
162    let raw_thread_handle =
163        unsafe { zx::Unowned::<'_, zx::Thread>::from_raw_handle(thrd_get_zx_handle(pthread)) };
164    *task_thread_guard = Some(Arc::new(
165        raw_thread_handle
166            .duplicate(zx::Rights::SAME_RIGHTS)
167            .expect("must have RIGHT_DUPLICATE on handle we created"),
168    ));
169    // Now that the task has a thread handle, update the thread's role using the policy configured.
170    drop(task_thread_guard);
171    if let Err(err) = ref_task.sync_scheduler_state_to_role() {
172        log_warn!(err:?; "Couldn't update freshly spawned thread's profile.");
173    }
174
175    // Record the thread and process ids for tracing after the task_thread is unlocked.
176    ref_task.record_pid_koid_mapping();
177
178    // Wait to send the `TaskBuilder` to the spawned thread until we know that it
179    // spawned successfully, as we need to ensure the builder is always explicitly
180    // released.
181    sender
182        .send(task_builder)
183        .expect("receiver should not be disconnected because thread spawned successfully");
184
185    Ok(())
186}
187
188unsafe extern "C" {
189    /// Sets the process handle used to create new threads, for the current thread.
190    fn thrd_set_zx_process(handle: zx::sys::zx_handle_t) -> zx::sys::zx_handle_t;
191
192    // Gets the thread handle underlying a specific thread.
193    // In C the 'thread' parameter is thrd_t which on Fuchsia is the same as pthread_t.
194    fn thrd_get_zx_handle(thread: u64) -> zx::sys::zx_handle_t;
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::ptrace::StopState;
201    use crate::signals::SignalInfo;
202    use crate::testing::*;
203    use starnix_uapi::signals::{SIGCONT, SIGSTOP};
204
205    #[::fuchsia::test]
206    async fn test_block_while_stopped_stop_and_continue() {
207        spawn_kernel_and_run(async |locked, task| {
208            // block_while_stopped must immediately returned if the task is not stopped.
209            task.block_while_stopped(locked);
210
211            // Stop the task.
212            task.thread_group().set_stopped(
213                StopState::GroupStopping,
214                Some(SignalInfo::kernel(SIGSTOP)),
215                false,
216            );
217
218            let thread = std::thread::spawn({
219                let task = task.weak_task();
220                move || {
221                    let task = task.upgrade().expect("task must be alive");
222                    // Wait for the task to have a waiter.
223                    while !task.read().is_blocked() {
224                        std::thread::sleep(std::time::Duration::from_millis(10));
225                    }
226
227                    // Continue the task.
228                    task.thread_group().set_stopped(
229                        StopState::Waking,
230                        Some(SignalInfo::kernel(SIGCONT)),
231                        false,
232                    );
233                }
234            });
235
236            // Block until continued.
237            task.block_while_stopped(locked);
238
239            // Join the thread, which will ensure set_stopped terminated.
240            thread.join().expect("joined");
241
242            // The task should not be blocked anymore.
243            task.block_while_stopped(locked);
244        })
245        .await;
246    }
247
248    #[::fuchsia::test]
249    async fn test_block_while_stopped_stop_and_exit() {
250        spawn_kernel_and_run(async |locked, task| {
251            // block_while_stopped must immediately returned if the task is neither stopped nor exited.
252            task.block_while_stopped(locked);
253
254            // Stop the task.
255            task.thread_group().set_stopped(
256                StopState::GroupStopping,
257                Some(SignalInfo::kernel(SIGSTOP)),
258                false,
259            );
260
261            let thread = std::thread::spawn({
262                let task = task.weak_task();
263                move || {
264                    #[allow(
265                        clippy::undocumented_unsafe_blocks,
266                        reason = "Force documented unsafe blocks in Starnix"
267                    )]
268                    let locked = unsafe { Unlocked::new() };
269                    let task = task.upgrade().expect("task must be alive");
270                    // Wait for the task to have a waiter.
271                    while !task.read().is_blocked() {
272                        std::thread::sleep(std::time::Duration::from_millis(10));
273                    }
274
275                    // exit the task.
276                    task.thread_group().exit(locked, ExitStatus::Exit(1), None);
277                }
278            });
279
280            // Block until continued.
281            task.block_while_stopped(locked);
282
283            // Join the task, which will ensure thread_group.exit terminated.
284            thread.join().expect("joined");
285
286            // The task should not be blocked because it is stopped.
287            task.block_while_stopped(locked);
288        })
289        .await;
290    }
291}