| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::{anyhow, Context, Error}, |
| fidl::endpoints::{ClientEnd, ServerEnd}, |
| fidl_fuchsia_component_runner::{ |
| self as fcrunner, ComponentControllerMarker, ComponentStartInfo, |
| }, |
| fidl_fuchsia_io as fio, fidl_fuchsia_starnix_developer as fstardev, fuchsia_async as fasync, |
| fuchsia_component::client as fclient, |
| fuchsia_component::server::ServiceFs, |
| fuchsia_zircon::{ |
| self as zx, sys::zx_exception_info_t, sys::zx_thread_state_general_regs_t, |
| sys::ZX_EXCEPTION_STATE_HANDLED, sys::ZX_EXCEPTION_STATE_TRY_NEXT, |
| sys::ZX_EXCP_POLICY_CODE_BAD_SYSCALL, |
| }, |
| futures::{StreamExt, TryStreamExt}, |
| io_util::directory, |
| log::{error, info}, |
| std::ffi::CString, |
| std::mem, |
| std::sync::Arc, |
| }; |
| |
| mod executive; |
| mod loader; |
| mod syscall_table; |
| mod syscalls; |
| mod uapi; |
| |
| use executive::*; |
| use loader::*; |
| use syscall_table::dispatch_syscall; |
| |
| // TODO: Should we move this code into fuchsia_zircon? It seems like part of a better abstraction |
| // for exception channels. |
| fn as_exception_info(buffer: &zx::MessageBuf) -> zx_exception_info_t { |
| let mut tmp = [0; mem::size_of::<zx_exception_info_t>()]; |
| tmp.clone_from_slice(buffer.bytes()); |
| unsafe { mem::transmute(tmp) } |
| } |
| |
| async fn run_process(process: Arc<ProcessContext>) -> Result<(), Error> { |
| let exceptions = &process.exceptions; |
| let mut buffer = zx::MessageBuf::new(); |
| while exceptions.recv_msg(&mut buffer).await.is_ok() { |
| let info = as_exception_info(&buffer); |
| assert!(buffer.n_handles() == 1); |
| let exception = zx::Exception::from(buffer.take_handle(0).unwrap()); |
| |
| if info.type_ != 0x8208 { |
| // ZX_EXCP_POLICY_ERROR |
| info!("exception type: 0x{:x}", info.type_); |
| exception.set_exception_state(&ZX_EXCEPTION_STATE_TRY_NEXT)?; |
| continue; |
| } |
| |
| let mut ctx = ThreadContext { |
| handle: exception.get_thread()?, |
| process: Arc::clone(&process), |
| registers: zx_thread_state_general_regs_t::default(), |
| }; |
| |
| let report = ctx.handle.get_exception_report()?; |
| |
| if report.context.synth_code != ZX_EXCP_POLICY_CODE_BAD_SYSCALL { |
| info!("exception synth_code: {}", report.context.synth_code); |
| exception.set_exception_state(&ZX_EXCEPTION_STATE_TRY_NEXT)?; |
| continue; |
| } |
| |
| let syscall_number = report.context.synth_data as u64; |
| ctx.registers = ctx.handle.read_state_general_regs()?; |
| let regs = &ctx.registers; |
| let args = (regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9); |
| // TODO(tbodt): Print the name of the syscall instead of its number (using a proc macro or |
| // something) |
| info!(target: "strace", "{}({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})", syscall_number, args.0, args.1, args.2, args.3, args.4, args.5); |
| match dispatch_syscall(&mut ctx, syscall_number, args) { |
| Ok(rv) => { |
| info!(target: "strace", "-> {:#x}", rv.value()); |
| ctx.registers.rax = rv.value(); |
| } |
| Err(errno) => { |
| info!(target: "strace", "!-> {}", errno); |
| ctx.registers.rax = (-errno.value()) as u64; |
| } |
| } |
| ctx.handle.write_state_general_regs(ctx.registers)?; |
| |
| exception.set_exception_state(&ZX_EXCEPTION_STATE_HANDLED)?; |
| } |
| |
| Ok(()) |
| } |
| |
| async fn start_component( |
| start_info: ComponentStartInfo, |
| _controller: ServerEnd<ComponentControllerMarker>, |
| ) -> Result<(), Error> { |
| info!( |
| "start_component: {}", |
| start_info.resolved_url.clone().unwrap_or("<unknown>".to_string()) |
| ); |
| |
| let root_path = runner::get_program_string(&start_info, "root") |
| .ok_or_else(|| anyhow!("No root in component manifest"))? |
| .to_owned(); |
| let binary_path = runner::get_program_binary(&start_info)?; |
| let ns = start_info.ns.ok_or_else(|| anyhow!("Missing namespace"))?; |
| |
| let pkg_proxy = ns |
| .into_iter() |
| .find(|entry| entry.path == Some("/pkg".to_string())) |
| .ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))? |
| .directory |
| .ok_or_else(|| anyhow!("Missing directory handlee in pkg namespace entry"))? |
| .into_proxy() |
| .context("failed to open /pkg")?; |
| let root_proxy = directory::open_directory( |
| &pkg_proxy, |
| &root_path, |
| fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE, |
| ) |
| .await |
| .context("failed to open root")?; |
| |
| let executable_vmo = library_loader::load_vmo(&root_proxy, &binary_path) |
| .await |
| .context("failed to load executable")?; |
| let (ldsvc_client, ldsvc_server) = zx::Channel::create()?; |
| library_loader::start( |
| directory::clone_no_describe( |
| &root_proxy, |
| Some(fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE), |
| )?, |
| ldsvc_server, |
| ); |
| let ldsvc_client = ClientEnd::new(ldsvc_client); |
| |
| let parent_job = fuchsia_runtime::job_default(); |
| let job = parent_job.create_child_job()?; |
| |
| let params = ProcessParameters { |
| name: CString::new(binary_path.clone())?, |
| argv: vec![CString::new(binary_path.clone())?], |
| environ: vec![], |
| }; |
| |
| run_process(Arc::new(load_executable(&job, executable_vmo, ldsvc_client, ¶ms).await?)) |
| .await?; |
| Ok(()) |
| } |
| |
| async fn start_runner( |
| mut request_stream: fcrunner::ComponentRunnerRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(event) = request_stream.try_next().await? { |
| match event { |
| fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => { |
| fasync::Task::local(async move { |
| start_component(start_info, controller) |
| .await |
| .expect("failed to start component") |
| }) |
| .detach(); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn start(url: String) -> Result<(), Error> { |
| let _instance = fclient::ScopedInstance::new("playground".to_string(), url).await?; |
| // Dropping _instance here will cause the component to be destroyed, but we |
| // don't know how to stop processes anyway, so starnix will keep running |
| // the program. Eventually, we'll keep track of all the running instances |
| // and be able to stop them. |
| Ok(()) |
| } |
| |
| async fn start_manager(mut request_stream: fstardev::ManagerRequestStream) -> Result<(), Error> { |
| while let Some(event) = request_stream.try_next().await? { |
| match event { |
| fstardev::ManagerRequest::Start { url, .. } => { |
| if let Err(e) = start(url).await { |
| error!("failed to start component: {}", e); |
| } |
| } |
| fstardev::ManagerRequest::StartShell { .. } => { |
| info!("StartShell not yet implemented.") |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| fuchsia_syslog::init_with_tags(&["starnix"]).expect("failed to initialize logger"); |
| info!("main"); |
| |
| let mut fs = ServiceFs::new_local(); |
| fs.dir("svc").add_fidl_service(move |stream| { |
| fasync::Task::local( |
| async move { start_runner(stream).await.expect("failed to start runner.") }, |
| ) |
| .detach(); |
| }); |
| fs.dir("svc").add_fidl_service(move |stream| { |
| fasync::Task::local(async move { |
| start_manager(stream).await.expect("failed to start manager.") |
| }) |
| .detach(); |
| }); |
| fs.take_and_serve_directory_handle()?; |
| fs.collect::<()>().await; |
| Ok(()) |
| } |