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