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