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