blob: e1e1b5064f6a968d79c40595bee9b936923bdcc2 [file] [log] [blame]
// Copyright 2019 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.
#include <lib/hermetic-compute/hermetic-compute.h>
#include <lib/zx/thread.h>
#include <zircon/assert.h>
#include <zircon/syscalls/debug.h>
#include <climits>
#include <cstdarg>
namespace {
void SetShadowCallStack(zx_thread_state_general_regs_t* regs, uintptr_t base, uintptr_t tos);
#ifdef __aarch64__
constexpr auto kPcRegister = &zx_thread_state_general_regs_t::pc;
constexpr auto kSpRegister = &zx_thread_state_general_regs_t::sp;
constexpr intptr_t kSpBias = 0;
constexpr auto kThreadRegister = &zx_thread_state_general_regs_t::tpidr;
constexpr bool kShadowCallStack = true;
void SetShadowCallStack(zx_thread_state_general_regs_t* regs, uintptr_t base, uintptr_t tos) {
// ShadowCallStack grows up and x18 points to the current horizon.
regs->r[18] = base;
}
template <size_t... I>
auto RegisterAccessors(std::index_sequence<I...>) {
struct Accessor {
size_t i;
auto& operator()(zx_thread_state_general_regs_t& regs) const { return regs.r[i]; }
};
return std::array<Accessor, sizeof...(I)>{Accessor{I}...};
}
auto ArgumentRegisters() { return RegisterAccessors(std::make_index_sequence<8>()); }
#elif defined(__x86_64__)
constexpr auto kPcRegister = &zx_thread_state_general_regs_t::rip;
constexpr auto kSpRegister = &zx_thread_state_general_regs_t::rsp;
constexpr intptr_t kSpBias = -8;
constexpr auto kThreadRegister = &zx_thread_state_general_regs_t::fs_base;
constexpr bool kShadowCallStack = false;
std::array<decltype(std::mem_fn(kSpRegister)), 6> ArgumentRegisters() {
return {
std::mem_fn(&zx_thread_state_general_regs_t::rdi),
std::mem_fn(&zx_thread_state_general_regs_t::rsi),
std::mem_fn(&zx_thread_state_general_regs_t::rdx),
std::mem_fn(&zx_thread_state_general_regs_t::rcx),
std::mem_fn(&zx_thread_state_general_regs_t::r8),
std::mem_fn(&zx_thread_state_general_regs_t::r9),
};
}
#else
#error "what machine?"
#endif
} // namespace
HermeticComputeProcess::Launcher::~Launcher() {
// Make sure a stillborn thread never starts running in user mode.
// The token is invalid if it's been handed off to an agent.
if (status_ != ZX_OK && token_) {
ZX_DEBUG_ASSERT(thread_);
thread_.kill();
}
}
zx_handle_t HermeticComputeProcess::Launcher::SendHandle(zx::handle handle) {
if (status_ == ZX_OK && thread_) {
// It's already been started, so it's too late.
status_ = ZX_ERR_BAD_STATE;
}
if (status_ == ZX_OK) {
status_ = engine_.Start(std::move(handle), &thread_, &token_);
}
if (status_ == ZX_OK) {
// Now fetch the registers to discover the remote handle value.
zx_thread_state_general_regs_t regs;
status_ = thread_.read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status_ == ZX_OK) {
return static_cast<zx_handle_t>(ArgumentRegisters()[0](regs));
}
}
return ZX_HANDLE_INVALID;
}
void HermeticComputeProcess::Launcher::Launch(size_t nargs, ...) {
// Bail out early if parameter packing reported errors.
if (status_ != ZX_OK) {
return;
}
// Called before LoadElf?
if (entry_pc_ == 0) {
status_ = ZX_ERR_BAD_STATE;
return;
}
zx_thread_state_general_regs_t regs{};
regs.*kPcRegister = entry_pc_;
// Allocate the stacks and TCB.
auto allocate = [&](size_t size, uintptr_t* base, uintptr_t* tos, size_t* tos_pos) {
zx::vmo vmo;
if (status_ == ZX_OK && size > 0) {
uintptr_t addr = 0;
status_ = engine_.LoadStack(&size, &vmo, &addr);
if (base) {
*base = addr;
}
if (tos) {
*tos = addr + size;
}
if (tos_pos) {
*tos_pos = size;
}
}
return vmo;
};
// The TCB points to the unsafe stack, which needs no other setup.
{
uintptr_t unsafe_sp = 0;
allocate(stack_size_, nullptr, &unsafe_sp, nullptr);
uintptr_t stack_guard = 0;
zx_cprng_draw(&stack_guard, sizeof(stack_guard));
uintptr_t tcb_ptr = 0;
zx::vmo tcb_vmo = allocate(sizeof(hermetic::Tcb), &tcb_ptr, nullptr, nullptr);
hermetic::Tcb tcb(tcb_ptr, stack_guard, unsafe_sp);
regs.*kThreadRegister = tcb_ptr + tcb.ThreadPointerOffset();
status_ = tcb_vmo.write(&tcb, 0, sizeof(tcb));
}
// The shadow call stack pointer goes directly into a register.
if constexpr (kShadowCallStack) {
uintptr_t scs_base = 0;
uintptr_t scs_tos = 0;
if (stack_size_ > 0) {
allocate(stack_size_, &scs_base, &scs_tos, nullptr);
}
SetShadowCallStack(&regs, scs_base, scs_tos);
}
// The first several arguments go directly into registers.
va_list args;
va_start(args, nargs);
for (const auto& regarg : ArgumentRegisters()) {
if (nargs == 0) {
break;
}
--nargs;
regarg(regs) = va_arg(args, uintptr_t);
}
// The machine stack is used for passing any remaining arguments.
// It's always kept double-word aligned.
const size_t arg_space = sizeof(uintptr_t) * ((nargs + 1) & -size_t{2});
uintptr_t stack_top = 0;
size_t stack_top_offset = 0;
zx::vmo stack_vmo =
allocate(std::max(stack_size_, arg_space - kSpBias), nullptr, &stack_top, &stack_top_offset);
for (size_t next = stack_top_offset - arg_space; status_ == ZX_OK && nargs > 0;
--nargs, next += sizeof(uintptr_t)) {
uintptr_t arg = va_arg(args, uintptr_t);
status_ = stack_vmo.write(&arg, next, sizeof(arg));
}
va_end(args);
regs.*kSpRegister = stack_top - arg_space + kSpBias;
// Now everything is in place in memory and the registers are known.
// If the thread hasn't been created yet, do it now.
if (status_ == ZX_OK && !thread_) {
status_ = engine_.Start({}, &thread_, &token_);
}
// Write the register state into the thread and then it's ready to run.
if (status_ == ZX_OK) {
status_ = thread_.write_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
}
// If there was a Suspended argument, it takes ownership of the
// thread and token handles and decides when to let it run.
if (status_ == ZX_OK && suspended_) {
*suspended_->thread = std::move(thread_);
*suspended_->token = std::move(token_);
}
}