blob: c3b6697320691aecd84a76612793698de0c672aa [file] [log] [blame]
// Copyright 2022 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};
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_io as fio;
use fidl_fuchsia_process as fprocess;
use fidl_fuchsia_starnix_developer as fstardev;
use fuchsia_runtime::{HandleInfo, HandleType};
use fuchsia_zircon::{self as zx, sys::ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET, HandleBased};
use process_builder::elf_parse;
use std::convert::TryFrom;
use std::sync::Arc;
use zerocopy::AsBytes;
use crate::fs::ext4::ExtFilesystem;
use crate::fs::fuchsia::{create_file_from_handle, RemoteFs, SyslogFile};
use crate::fs::*;
use crate::mm::{DesiredAddress, MappingOptions, PAGE_SIZE};
use crate::task::*;
use crate::types::*;
use crate::vmex_resource::VMEX_RESOURCE;
/// Sets the ZX_PROP_PROCESS_DEBUG_ADDR of the process.
///
/// Sets the process property if a valid address is found in the `DT_DEBUG` entry. If the existing
/// value of ZX_PROP_PROCESS_DEBUG_ADDR is set to ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET, the task will
/// be set up to trigger a software interrupt (for the debugger to catch) before resuming execution
/// at the current instruction pointer.
///
/// If the property is set on the process (i.e., nothing fails and the values are valid),
/// `current_task.debug_address` will be cleared.
///
/// # Parameters:
/// - `current_task`: The task to set the property for. The register's of this task, the instruction
/// pointer specifically, needs to be set to the value with which the task is
/// expected to resume.
pub fn set_process_debug_addr(current_task: &mut CurrentTask) -> Result<(), Errno> {
let dt_debug_address = match current_task.dt_debug_address {
Some(dt_debug_address) => dt_debug_address,
// The DT_DEBUG entry does not exist, or has already been read and set on the process.
None => return Ok(()),
};
// The debug_addres is the pointer located at DT_DEBUG.
let debug_address: elf_parse::Elf64Dyn =
current_task.mm.read_object(UserRef::new(dt_debug_address))?;
if debug_address.value == 0 {
// The DT_DEBUG entry is present, but has not yet been set by the linker, check next time.
return Ok(());
}
let existing_debug_addr = current_task
.thread_group
.process
.get_debug_addr()
.map_err(|err| from_status_like_fdio!(err))?;
// If existing_debug_addr != ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET then there is no reason to
// insert the interrupt.
if existing_debug_addr != ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET as u64 {
// Still set the debug address, and clear the debug address from `current_task` to avoid
// entering this function again.
match current_task.thread_group.process.set_debug_addr(&debug_address.value) {
Err(zx::Status::ACCESS_DENIED) => {}
status => status.map_err(|err| from_status_like_fdio!(err))?,
};
current_task.dt_debug_address = None;
return Ok(());
}
// An executable VMO is mapped into the process, which does two things:
// 1. Issues a software interrupt caught by the debugger.
// 2. Jumps back to the current instruction pointer of the thread.
#[cfg(target_arch = "x86_64")]
const INTERRUPT_AND_JUMP: [u8; 7] = [
0xcc, // int 3
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp *0x0(%rip)
];
let mut instruction_pointer = current_task.registers.rip.as_bytes().to_owned();
let mut instructions = INTERRUPT_AND_JUMP.to_vec();
instructions.append(&mut instruction_pointer);
let vmo = Arc::new(
zx::Vmo::create(*PAGE_SIZE)
.and_then(|vmo| vmo.replace_as_executable(&VMEX_RESOURCE))
.map_err(|err| from_status_like_fdio!(err))?,
);
vmo.write(&instructions, 0).map_err(|e| from_status_like_fdio!(e))?;
let instruction_pointer = current_task.mm.map(
DesiredAddress::Hint(UserAddress::default()),
vmo,
0,
instructions.len(),
zx::VmarFlags::PERM_EXECUTE | zx::VmarFlags::PERM_READ,
MappingOptions::empty(),
None,
)?;
current_task.registers.rip = instruction_pointer.ptr() as u64;
current_task
.thread_group
.process
.set_debug_addr(&debug_address.value)
.map_err(|err| from_status_like_fdio!(err))?;
current_task.dt_debug_address = None;
Ok(())
}
pub fn copy_process_debug_addr(
source_process: &zx::Process,
target_process: &zx::Process,
) -> Result<(), Errno> {
let source_debug_addr =
source_process.get_debug_addr().map_err(|err| from_status_like_fdio!(err))?;
let target_debug_addr =
target_process.get_debug_addr().map_err(|err| from_status_like_fdio!(err))?;
// TODO: Handle the case where either of the debug address requires to set an interrupt.
if source_debug_addr == ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET as u64 {
return Ok(());
}
if target_debug_addr == ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET as u64 {
return Ok(());
}
match target_process.set_debug_addr(&source_debug_addr) {
Err(zx::Status::ACCESS_DENIED) => {}
status => status.map_err(|err| from_status_like_fdio!(err))?,
};
Ok(())
}
pub struct StartupHandles {
pub shell_controller: Option<ServerEnd<fstardev::ShellControllerMarker>>,
}
/// Creates a `StartupHandles` from the provided handles.
///
/// The `numbered_handles` of type `HandleType::FileDescriptor` are used to
/// create files, and the handles are required to be of type `zx::Socket`.
///
/// If there is a `numbered_handles` of type `HandleType::User0`, that is
/// interpreted as the server end of the ShellController protocol.
pub fn parse_numbered_handles(
current_task: &CurrentTask,
numbered_handles: Option<Vec<fprocess::HandleInfo>>,
files: &Arc<FdTable>,
) -> Result<StartupHandles, Error> {
let mut shell_controller = None;
if let Some(numbered_handles) = numbered_handles {
for numbered_handle in numbered_handles {
let info = HandleInfo::try_from(numbered_handle.id)?;
if info.handle_type() == HandleType::FileDescriptor {
files.insert(
FdNumber::from_raw(info.arg().into()),
create_file_from_handle(current_task, numbered_handle.handle)?,
);
} else if info.handle_type() == HandleType::User0 {
shell_controller = Some(ServerEnd::<fstardev::ShellControllerMarker>::from(
numbered_handle.handle,
));
}
}
} else {
// If no numbered handles are provided default 0, 1, and 2 to a syslog file.
let stdio = SyslogFile::new(current_task);
files.insert(FdNumber::from_raw(0), stdio.clone());
files.insert(FdNumber::from_raw(1), stdio.clone());
files.insert(FdNumber::from_raw(2), stdio);
}
Ok(StartupHandles { shell_controller })
}
/// Creates a `HandleInfo` from the provided socket and file descriptor.
///
/// The file descriptor is encoded as a `PA_HND(PA_FD, <file_descriptor>)` before being stored in
/// the `HandleInfo`.
///
/// Returns an error if `socket` is `None`.
pub fn handle_info_from_socket(
socket: Option<fidl::Socket>,
file_descriptor: u16,
) -> Result<fprocess::HandleInfo, Error> {
if let Some(socket) = socket {
let info = HandleInfo::new(HandleType::FileDescriptor, file_descriptor);
Ok(fprocess::HandleInfo { handle: socket.into_handle(), id: info.as_raw() })
} else {
Err(anyhow!("Failed to create HandleInfo for {}", file_descriptor))
}
}
/// Create a filesystem to access the content of the fuchsia directory available at `fs_src` inside
/// `pkg`.
pub fn create_remotefs_filesystem(
root: &fio::DirectorySynchronousProxy,
fs_src: &str,
) -> Result<FileSystemHandle, Error> {
let rights = fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE;
let root = syncio::directory_open_directory_async(root, fs_src, rights)
.map_err(|e| anyhow!("Failed to open root: {}", e))?;
Ok(RemoteFs::new(root.into_channel(), rights)?)
}
/// Returns a hash representing the fuchsia package `pkg`.
///
/// The implementation is hashing /meta/content
pub fn get_pkg_hash(pkg: &fio::DirectorySynchronousProxy) -> Result<String, Error> {
let buffer = syncio::directory_read_file(pkg, "/meta", zx::Time::INFINITE)?;
let hash = std::str::from_utf8(&buffer)?;
Ok(hash.to_string())
}
pub fn create_filesystem_from_spec<'a>(
kernel: &Arc<Kernel>,
task: Option<&CurrentTask>,
pkg: &fio::DirectorySynchronousProxy,
spec: &'a str,
) -> Result<(&'a [u8], WhatToMount), Error> {
use WhatToMount::*;
let mut iter = spec.splitn(3, ':');
let mount_point =
iter.next().ok_or_else(|| anyhow!("mount point is missing from {:?}", spec))?;
let fs_type = iter.next().ok_or_else(|| anyhow!("fs type is missing from {:?}", spec))?;
let fs_src = iter.next().unwrap_or(".");
// The filesystem types handled in this match are the ones that can only be specified in a
// manifest file, for whatever reason. Anything else is passed to create_filesystem, which is
// common code that also handles the mount() system call.
let fs = match fs_type {
"bind" => {
let task = task.ok_or(errno!(ENOENT))?;
Dir(task.lookup_path_from_root(fs_src.as_bytes())?.entry)
}
"remotefs" => Fs(create_remotefs_filesystem(pkg, &fs_src)?),
"ext4" => {
let vmo =
syncio::directory_open_vmo(&pkg, &fs_src, fio::VmoFlags::READ, zx::Time::INFINITE)
.context("failed to open EXT4 image file")?;
Fs(ExtFilesystem::new(vmo)?)
}
_ => create_filesystem(&kernel, fs_src.as_bytes(), fs_type.as_bytes(), b"")?,
};
Ok((mount_point.as_bytes(), fs))
}