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