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