blob: 8aa98fce64cd70c637929d2c3a8561d802cb1cd7 [file] [log] [blame]
// 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 fidl_fuchsia_io as fio;
use fuchsia_zircon::{self as zx, sys::zx_thread_state_general_regs_t, HandleBased, Status};
use parking_lot::RwLock;
use std::sync::Arc;
use crate::types::*;
use crate::fs::FdTable;
pub struct ProgramBreak {
vmar: zx::Vmar,
vmo: zx::Vmo,
// These base address at which the data segment is mapped.
base: UserAddress,
// The current program break.
//
// The addresses from [base, current.round_up(PAGE_SIZE)) are mapped into the
// client address space from the underlying |vmo|.
current: UserAddress,
}
impl Default for ProgramBreak {
fn default() -> ProgramBreak {
return ProgramBreak {
vmar: zx::Handle::invalid().into(),
vmo: zx::Handle::invalid().into(),
base: UserAddress::default(),
current: UserAddress::default(),
};
}
}
const PROGRAM_BREAK_LIMIT: u64 = 64 * 1024 * 1024;
const PAGE_SIZE: u64 = 4 * 1024;
pub struct MemoryManager {
pub root_vmar: zx::Vmar,
program_break: RwLock<ProgramBreak>,
}
impl MemoryManager {
pub fn new(root_vmar: zx::Vmar) -> Self {
MemoryManager { root_vmar, program_break: RwLock::new(ProgramBreak::default()) }
}
pub fn set_program_break(&self, addr: UserAddress) -> Result<UserAddress, Status> {
let mut program_break = self.program_break.write();
if program_break.vmar.is_invalid_handle() {
// TODO: This allocation places the program break at a random location in the
// child's address space. However, we're supposed to put this memory directly
// above the last segment of the ELF for the child.
let (vmar, raw_addr) = self.root_vmar.allocate(
0,
PROGRAM_BREAK_LIMIT as usize,
zx::VmarFlags::CAN_MAP_SPECIFIC
| zx::VmarFlags::CAN_MAP_READ
| zx::VmarFlags::CAN_MAP_WRITE,
)?;
let vmo = zx::Vmo::create(PROGRAM_BREAK_LIMIT)?;
program_break.vmar = vmar;
program_break.vmo = vmo;
program_break.base = UserAddress::from(raw_addr);
program_break.current = program_break.base;
}
if addr < program_break.base || addr > program_break.base + PROGRAM_BREAK_LIMIT {
// The requested program break is out-of-range. We're supposed to simply
// return the current program break.
return Ok(program_break.current);
}
if addr < program_break.current {
// The client wishes to free up memory. Adjust the program break to the new
// location.
let aligned_previous = program_break.current.round_up(PAGE_SIZE);
program_break.current = addr;
let aligned_current = program_break.current.round_up(PAGE_SIZE);
let len = aligned_current - aligned_previous;
if len > 0 {
// If we crossed a page boundary, we can actually unmap and free up the
// unused pages.
let offset = aligned_previous - program_break.base;
unsafe { program_break.vmar.unmap(aligned_current.ptr(), len)? };
program_break.vmo.op_range(zx::VmoOp::DECOMMIT, offset as u64, len as u64)?;
}
return Ok(program_break.current);
}
// Otherwise, we've been asked to increase the page break.
let aligned_previous = program_break.current.round_up(PAGE_SIZE);
program_break.current = addr;
let aligned_current = program_break.current.round_up(PAGE_SIZE);
let len = aligned_current - aligned_previous;
if len > 0 {
// If we crossed a page boundary, we need to map more of the underlying VMO
// into the client's address space.
let offset = aligned_previous - program_break.base;
program_break.vmar.map(
offset,
&program_break.vmo,
offset as u64,
len,
zx::VmarFlags::PERM_READ
| zx::VmarFlags::PERM_WRITE
| zx::VmarFlags::REQUIRE_NON_RESIZABLE
| zx::VmarFlags::SPECIFIC,
)?;
}
return Ok(program_break.current);
}
}
#[derive(Default)]
pub struct SecurityContext {
pub uid: uid_t,
pub gid: uid_t,
pub euid: uid_t,
pub egid: uid_t,
}
// TODO(tbodt): merge ProcessContext and ThreadContext into a single struct corresponding to struct
// task_struct in Linux
pub struct ProcessContext {
pub handle: zx::Process,
pub exceptions: zx::Channel,
pub security: SecurityContext,
pub mm: MemoryManager,
// TODO: Replace with a real VFS. This can't last long.
pub root: fio::DirectoryProxy,
/// Corresponds to struct task_struct->files in Linux.
pub fd_table: FdTable,
}
impl ProcessContext {
pub fn read_memory(&self, addr: UserAddress, bytes: &mut [u8]) -> Result<(), Errno> {
let actual = self.handle.read_memory(addr.ptr(), bytes).map_err(|_| EFAULT)?;
if actual != bytes.len() {
return Err(EFAULT);
}
Ok(())
}
pub fn read_c_string<'a>(&self, addr: UserAddress, buffer: &'a mut [u8]) -> Result<&'a [u8], Errno> {
let actual = self.handle.read_memory(addr.ptr(), buffer).map_err(|_| EFAULT)?;
let buffer = &mut buffer[..actual];
let null_index = memchr::memchr(b'\0', buffer).ok_or(ENAMETOOLONG)?;
Ok(&buffer[..null_index])
}
pub fn write_memory(&self, addr: UserAddress, bytes: &[u8]) -> Result<(), Errno> {
let actual = self.handle.write_memory(addr.ptr(), bytes).map_err(|_| EFAULT)?;
if actual != bytes.len() {
return Err(EFAULT);
}
Ok(())
}
}
pub struct ThreadContext {
pub handle: zx::Thread,
pub process: Arc<ProcessContext>,
pub registers: zx_thread_state_general_regs_t,
}