blob: 2aa2f31351e80965e87ce0b3c4bfce0d42339e02 [file] [log] [blame]
// Copyright 2016 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 "process.h"
#include <cinttypes>
#include <link.h>
#include <launchpad/vmo.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <fdio/io.h>
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "garnet/lib/debugger_utils/util.h"
#include "server.h"
namespace debugserver {
namespace {
constexpr zx_time_t kill_timeout = ZX_MSEC(10 * 1000);
bool SetupLaunchpad(launchpad_t** out_lp, const util::Argv& argv) {
FXL_DCHECK(out_lp);
FXL_DCHECK(argv.size() > 0);
// Construct the argument array.
const char* c_args[argv.size()];
for (size_t i = 0; i < argv.size(); ++i)
c_args[i] = argv[i].c_str();
const char* name = util::basename(c_args[0]);
launchpad_t* lp = nullptr;
zx_status_t status = launchpad_create(0u, name, &lp);
if (status != ZX_OK)
goto fail;
status = launchpad_set_args(lp, argv.size(), c_args);
if (status != ZX_OK)
goto fail;
status = launchpad_add_vdso_vmo(lp);
if (status != ZX_OK)
goto fail;
// Clone root, cwd, stdio, and environ.
launchpad_clone(lp, LP_CLONE_FDIO_ALL | LP_CLONE_ENVIRON);
*out_lp = lp;
return true;
fail:
FXL_LOG(ERROR) << "Process setup failed: " << util::ZxErrorString(status);
if (lp)
launchpad_destroy(lp);
return false;
}
bool LoadBinary(launchpad_t* lp, const std::string& binary_path) {
FXL_DCHECK(lp);
zx_handle_t vmo;
zx_status_t status = launchpad_vmo_from_file(binary_path.c_str(), &vmo);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Could not load binary: " << util::ZxErrorString(status);
return false;
}
status = launchpad_elf_load(lp, vmo);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Could not load binary: " << util::ZxErrorString(status);
return false;
}
status = launchpad_load_vdso(lp, ZX_HANDLE_INVALID);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Could not load vDSO: " << util::ZxErrorString(status);
return false;
}
return true;
}
zx_koid_t GetProcessId(launchpad_t* lp) {
FXL_DCHECK(lp);
// We use the zx_object_get_child syscall to obtain a debug-capable handle
// to the process. For processes, the syscall expect the ID of the underlying
// kernel object (koid, also passing for process id in Zircon).
zx_handle_t process_handle = launchpad_get_process_handle(lp);
FXL_DCHECK(process_handle);
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(process_handle, ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "zx_object_get_info_failed: "
<< util::ZxErrorString(status);
return ZX_KOID_INVALID;
}
FXL_DCHECK(info.type == ZX_OBJ_TYPE_PROCESS);
return info.koid;
}
zx_handle_t GetProcessDebugHandle(zx_koid_t pid) {
zx_handle_t handle = ZX_HANDLE_INVALID;
zx_status_t status = zx_object_get_child(ZX_HANDLE_INVALID, pid,
ZX_RIGHT_SAME_RIGHTS, &handle);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "zx_object_get_child failed: "
<< util::ZxErrorString(status);
return ZX_HANDLE_INVALID;
}
// TODO(armansito): Check that |handle| has ZX_RIGHT_DEBUG (this seems
// not to be set by anything at the moment but eventully we should check)?
// Syscalls shouldn't return ZX_HANDLE_INVALID in the case of ZX_OK.
FXL_DCHECK(handle != ZX_HANDLE_INVALID);
FXL_VLOG(1) << "Handle " << handle << " obtained for process " << pid;
return handle;
}
} // namespace
// static
const char* Process::StateName(Process::State state) {
#define CASE_TO_STR(x) \
case Process::State::x: \
return #x
switch (state) {
CASE_TO_STR(kNew);
CASE_TO_STR(kStarting);
CASE_TO_STR(kRunning);
CASE_TO_STR(kGone);
default:
break;
}
#undef CASE_TO_STR
return "(unknown)";
}
Process::Process(Server* server, Delegate* delegate)
: server_(server),
delegate_(delegate),
memory_(std::shared_ptr<util::ByteBlock>(new ProcessMemory(this))),
breakpoints_(this) {
FXL_DCHECK(server_);
FXL_DCHECK(delegate_);
}
Process::~Process() {
// If we're still attached then either kill the process if we
// started it or detach if we attached to it after it was running.
if (attached_running_) {
RawDetach();
} else {
if (!Kill()) {
// Paranoia: Still need to detach before we can call Clear().
RawDetach();
}
}
Clear();
}
std::string Process::GetName() const {
return fxl::StringPrintf("%" PRId64, id());
}
bool Process::Initialize() {
FXL_DCHECK(!launchpad_);
FXL_DCHECK(!handle_);
FXL_DCHECK(!eport_key_);
zx_status_t status;
// The Process object survives run-after-run. Switch Gone back to New.
switch (state_) {
case State::kNew:
break;
case State::kGone:
set_state(State::kNew);
break;
default:
// Shouldn't get here if process is currently live.
FXL_DCHECK(false);
}
FXL_LOG(INFO) << "Initializing process";
attached_running_ = false;
if (argv_.size() == 0 || argv_[0].size() == 0) {
FXL_LOG(ERROR) << "No program specified";
return false;
}
FXL_LOG(INFO) << "argv: " << util::ArgvToString(argv_);
if (!SetupLaunchpad(&launchpad_, argv_))
return false;
FXL_LOG(INFO) << "Process setup complete";
if (!LoadBinary(launchpad_, argv_[0]))
goto fail;
FXL_VLOG(1) << "Binary loaded";
// Initialize the PID.
id_ = GetProcessId(launchpad_);
FXL_DCHECK(id_ != ZX_KOID_INVALID);
status = launchpad_get_base_address(launchpad_, &base_address_);
if (status != ZX_OK) {
FXL_LOG(ERROR)
<< "Failed to obtain the dynamic linker base address for process: "
<< util::ZxErrorString(status);
goto fail;
}
status = launchpad_get_entry_address(launchpad_, &entry_address_);
if (status != ZX_OK) {
FXL_LOG(ERROR)
<< "Failed to obtain the dynamic linker entry address for process: "
<< util::ZxErrorString(status);
goto fail;
}
FXL_LOG(INFO) << "Obtained base load address: "
<< fxl::StringPrintf("0x%" PRIxPTR, base_address_)
<< ", entry address: "
<< fxl::StringPrintf("0x%" PRIxPTR, entry_address_);
return true;
fail:
id_ = ZX_KOID_INVALID;
launchpad_destroy(launchpad_);
launchpad_ = nullptr;
return false;
}
// TODO(dje): Merge common parts with Initialize() after things settle down.
bool Process::Initialize(zx_koid_t pid) {
FXL_DCHECK(!launchpad_);
FXL_DCHECK(!handle_);
FXL_DCHECK(!eport_key_);
// The Process object survives run-after-run. Switch Gone back to New.
switch (state_) {
case State::kNew:
break;
case State::kGone:
set_state(State::kNew);
break;
default:
// Shouldn't get here if process is currently live.
FXL_DCHECK(false);
}
FXL_LOG(INFO) << "Initializing process";
attached_running_ = true;
id_ = pid;
FXL_LOG(INFO) << "Process setup complete";
return true;
}
bool Process::AllocDebugHandle() {
FXL_DCHECK(handle_ == ZX_HANDLE_INVALID);
auto handle = GetProcessDebugHandle(id_);
if (handle == ZX_HANDLE_INVALID)
return false;
handle_ = handle;
return true;
}
void Process::CloseDebugHandle() {
zx_handle_close(handle_);
handle_ = ZX_HANDLE_INVALID;
}
bool Process::BindExceptionPort() {
ExceptionPort::Key key = server_->exception_port()->Bind(
handle_,
std::bind(&Process::OnException, this, std::placeholders::_1,
std::placeholders::_2));
if (!key)
return false;
eport_key_ = key;
return true;
}
void Process::UnbindExceptionPort() {
FXL_DCHECK(eport_key_);
if (!server_->exception_port()->Unbind(eport_key_))
FXL_LOG(WARNING) << "Failed to unbind exception port; ignoring";
eport_key_ = 0;
}
bool Process::Attach() {
if (IsAttached()) {
FXL_LOG(ERROR) << "Cannot attach an already attached process";
return false;
}
FXL_LOG(INFO) << "Attaching to process " << id();
if (!AllocDebugHandle())
return false;
if (!BindExceptionPort()) {
CloseDebugHandle();
return false;
}
if (attached_running_) {
set_state(State::kRunning);
thread_map_stale_ = true;
}
return true;
}
void Process::RawDetach() {
// A copy of the handle is kept in ExceptionPort.BindData.
// We can't close the process handle until we unbind the exception port,
// so verify it's still open.
FXL_DCHECK(handle_);
FXL_DCHECK(IsAttached());
FXL_LOG(INFO) << "Detaching from process " << id();
UnbindExceptionPort();
CloseDebugHandle();
}
bool Process::Detach() {
if (!IsAttached()) {
FXL_LOG(ERROR) << "Not attached";
return false;
}
RawDetach();
Clear();
return true;
}
bool Process::Start() {
FXL_DCHECK(launchpad_);
FXL_DCHECK(handle_);
if (state_ != State::kNew) {
FXL_LOG(ERROR) << "Process already started";
return false;
}
// launchpad_go returns a dup of the process handle (owned by
// |launchpad_|), where the original handle is given to the child. We have to
// close the dup handle to avoid leaking it.
zx_handle_t dup_handle;
zx_status_t status = launchpad_go(launchpad_, &dup_handle, nullptr);
// Launchpad is no longer needed after launchpad_go returns.
launchpad_ = nullptr;
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start inferior process: "
<< util::ZxErrorString(status);
return false;
}
zx_handle_close(dup_handle);
set_state(State::kStarting);
return true;
}
bool Process::Kill() {
// If the caller wants to flag an error if the process isn't running s/he
// can, but for our purposes here we're more forgiving.
switch (state_) {
case Process::State::kNew:
case Process::State::kGone:
FXL_VLOG(1) << "Process is not live";
return true;
default:
break;
}
FXL_LOG(INFO) << "Killing process " << id();
// There's a few issues with sequencing here that we need to consider.
// - OnProcessExit, called when we receive an exception indicating
// the process has exited, will send back a stop reply which we don't want
// - we don't want to unbind the exception port before killing the process
// because we don't want to accidently cause the process to resume before
// we kill it
// - we need the debug handle to kill the process
FXL_DCHECK(handle_ != ZX_HANDLE_INVALID);
auto status = zx_task_kill(handle_);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to kill process: " << util::ZxErrorString(status);
return false;
}
UnbindExceptionPort();
zx_signals_t signals;
// If something goes wrong we don't want to wait forever.
status = zx_object_wait_one(handle_, ZX_TASK_TERMINATED,
zx_deadline_after(kill_timeout), &signals);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error waiting for process to die, ignoring: "
<< util::ZxErrorString(status);
} else {
FXL_DCHECK(signals & ZX_TASK_TERMINATED);
}
CloseDebugHandle();
Clear();
return true;
}
void Process::set_state(State new_state) {
switch (new_state) {
case State::kNew:
FXL_DCHECK(state_ == State::kGone);
break;
case State::kStarting:
FXL_DCHECK(state_ == State::kNew);
break;
case State::kRunning:
FXL_DCHECK(state_ == State::kNew || state_ == State::kStarting);
break;
case State::kGone:
break;
default:
FXL_DCHECK(false);
}
state_ = new_state;
}
void Process::Clear() {
// The process must already be fully detached from.
FXL_DCHECK(!IsAttached());
threads_.clear();
thread_map_stale_ = false;
id_ = ZX_KOID_INVALID;
base_address_ = 0;
entry_address_ = 0;
attached_running_ = false;
dso_free_list(dsos_);
dsos_ = nullptr;
dsos_build_failed_ = false;
if (launchpad_)
launchpad_destroy(launchpad_);
launchpad_ = nullptr;
// The process may just exited or whatever. Force the state to kGone.
set_state(State::kGone);
}
bool Process::IsLive() const {
return state_ != State::kNew && state_ != State::kGone;
}
bool Process::IsAttached() const {
if (eport_key_) {
FXL_DCHECK(handle_ != ZX_HANDLE_INVALID);
return true;
} else {
FXL_DCHECK(handle_ == ZX_HANDLE_INVALID);
return false;
}
}
void Process::EnsureThreadMapFresh() {
if (thread_map_stale_) {
RefreshAllThreads();
}
}
Thread* Process::FindThreadById(zx_koid_t thread_id) {
FXL_DCHECK(handle_);
if (thread_id == ZX_HANDLE_INVALID) {
FXL_LOG(ERROR) << "Invalid thread ID given: " << thread_id;
return nullptr;
}
EnsureThreadMapFresh();
const auto iter = threads_.find(thread_id);
if (iter != threads_.end()) {
Thread* thread = iter->second.get();
if (thread->state() == Thread::State::kGone) {
FXL_VLOG(1) << "FindThreadById: Thread " << thread->GetDebugName()
<< " is gone";
return nullptr;
}
return thread;
}
// Try to get a debug capable handle to the child of the current process with
// a kernel object ID that matches |thread_id|.
zx_handle_t thread_handle;
zx_status_t status = zx_object_get_child(
handle_, thread_id, ZX_RIGHT_SAME_RIGHTS, &thread_handle);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Could not obtain a debug handle to thread: "
<< util::ZxErrorString(status);
return nullptr;
}
Thread* thread = new Thread(this, thread_handle, thread_id);
threads_[thread_id] = std::unique_ptr<Thread>(thread);
return thread;
}
Thread* Process::PickOneThread() {
EnsureThreadMapFresh();
if (threads_.empty())
return nullptr;
return threads_.begin()->second.get();
}
bool Process::RefreshAllThreads() {
FXL_DCHECK(handle_);
// First get the thread count so that we can allocate an appropriately sized
// buffer. This is racy but unless the caller stops all threads that's just
// the way things are.
size_t num_threads;
zx_status_t status =
zx_object_get_info(handle_, ZX_INFO_PROCESS_THREADS, nullptr, 0,
nullptr, &num_threads);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to get process thread info (#threads): "
<< util::ZxErrorString(status);
return false;
}
auto buffer_size = num_threads * sizeof(zx_koid_t);
auto koids = std::make_unique<zx_koid_t[]>(num_threads);
size_t records_read;
status = zx_object_get_info(handle_, ZX_INFO_PROCESS_THREADS,
koids.get(), buffer_size, &records_read, nullptr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to get process thread info: "
<< util::ZxErrorString(status);
return false;
}
FXL_DCHECK(records_read == num_threads);
ThreadMap new_threads;
for (size_t i = 0; i < num_threads; ++i) {
zx_koid_t thread_id = koids[i];
zx_handle_t thread_handle = ZX_HANDLE_INVALID;
status = zx_object_get_child(handle_, thread_id, ZX_RIGHT_SAME_RIGHTS,
&thread_handle);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Could not obtain a debug handle to thread: "
<< util::ZxErrorString(status);
continue;
}
new_threads[thread_id] =
std::make_unique<Thread>(this, thread_handle, thread_id);
}
// Just clear the existing list and repopulate it.
threads_ = std::move(new_threads);
thread_map_stale_ = false;
return true;
}
void Process::ForEachThread(const ThreadCallback& callback) {
EnsureThreadMapFresh();
for (const auto& iter : threads_)
callback(iter.second.get());
}
void Process::ForEachLiveThread(const ThreadCallback& callback) {
EnsureThreadMapFresh();
for (const auto& iter : threads_) {
Thread* thread = iter.second.get();
if (thread->state() != Thread::State::kGone)
callback(thread);
}
}
bool Process::ReadMemory(uintptr_t address, void* out_buffer, size_t length) {
return memory_->Read(address, out_buffer, length);
}
bool Process::WriteMemory(uintptr_t address, const void* data, size_t length) {
return memory_->Write(address, data, length);
}
void Process::TryBuildLoadedDsosList(Thread* thread, bool check_ldso_bkpt) {
FXL_DCHECK(dsos_ == nullptr);
FXL_VLOG(2) << "Building dso list";
uintptr_t debug_addr;
zx_handle_t process_handle = thread->process()->handle();
zx_status_t status = zx_object_get_property(process_handle,
ZX_PROP_PROCESS_DEBUG_ADDR,
&debug_addr, sizeof(debug_addr));
if (status != ZX_OK) {
FXL_LOG(ERROR) << "zx_object_get_property failed, unable to fetch dso list: "
<< util::ZxErrorString(status);
return;
}
struct r_debug debug;
if (!ReadMemory(debug_addr, &debug, sizeof(debug))) {
FXL_VLOG(2) << "unable to read _dl_debug_addr";
// Don't set dsos_build_failed_ here, it may be too early to try.
return;
}
// Since we could, theoretically, stop in the dynamic linker before we get
// that far check to see if it has been filled in.
// TODO(dje): Document our test in dynlink.c.
if (debug.r_version == 0) {
FXL_VLOG(2) << "debug.r_version is 0";
// Don't set dsos_build_failed_ here, it may be too early to try.
return;
}
if (check_ldso_bkpt) {
FXL_DCHECK(thread);
bool success = thread->registers()->RefreshGeneralRegisters();
FXL_DCHECK(success);
zx_vaddr_t pc = thread->registers()->GetPC();
// TODO(dje): -1: adjust_pc_after_break
if (pc - 1 != debug.r_brk) {
FXL_VLOG(2) << "not stopped at dynamic linker debug breakpoint";
return;
}
} else {
FXL_DCHECK(!thread);
}
auto lmap_vaddr = reinterpret_cast<zx_vaddr_t>(debug.r_map);
dsos_ = util::dso_fetch_list(memory_, lmap_vaddr, "app");
// We should have fetched at least one since this is not called until the
// dl_debug_state breakpoint is hit.
if (dsos_ == nullptr) {
// Don't keep trying.
FXL_VLOG(2) << "dso_fetch_list failed";
dsos_build_failed_ = true;
} else {
util::dso_vlog_list(dsos_);
// This may already be false, but set it any for documentation purposes.
dsos_build_failed_ = false;
}
}
void Process::OnException(const zx_port_packet_t& packet,
const zx_exception_context_t& context) {
zx_excp_type_t type = static_cast<zx_excp_type_t>(packet.type);
Thread* thread = nullptr;
if (packet.exception.tid != ZX_KOID_INVALID)
thread = FindThreadById(packet.exception.tid);
// Finding the load address of the main executable requires a few steps.
// It's not loaded until the first time we hit the _dl_debug_state
// breakpoint. For now gdb sets that breakpoint. What we do is watch for
// s/w breakpoint exceptions.
if (type == ZX_EXCP_SW_BREAKPOINT) {
FXL_DCHECK(thread);
if (!DsosLoaded() && !dsos_build_failed_)
TryBuildLoadedDsosList(thread, true);
}
// |type| could either map to an architectural exception or Zircon-defined
// synthetic exceptions.
if (ZX_EXCP_IS_ARCH(type)) {
FXL_DCHECK(thread);
thread->OnException(type, context);
delegate_->OnArchitecturalException(this, thread, type, context);
return;
}
switch (type) {
case ZX_EXCP_THREAD_STARTING:
FXL_VLOG(1) << "Received ZX_EXCP_THREAD_STARTING exception";
FXL_DCHECK(thread);
FXL_DCHECK(thread->state() == Thread::State::kNew);
thread->OnException(type, context);
delegate_->OnThreadStarting(this, thread, context);
break;
case ZX_EXCP_THREAD_EXITING:
FXL_DCHECK(thread);
FXL_VLOG(1) << "Received ZX_EXCP_THREAD_EXITING exception for thread "
<< thread->GetName();
thread->OnException(type, context);
delegate_->OnThreadExiting(this, thread, context);
break;
case ZX_EXCP_POLICY_ERROR:
FXL_DCHECK(thread);
FXL_VLOG(1) << "Received ZX_EXCP_POLICY_ERROR exception for thread "
<< thread->GetName();
thread->OnException(type, context);
delegate_->OnSyntheticException(this, thread, type, context);
break;
default:
FXL_LOG(ERROR) << "Ignoring unrecognized synthetic exception: " << type;
break;
}
}
int Process::ExitCode() {
FXL_DCHECK(state_ == State::kGone);
zx_info_process_t info;
auto status = zx_object_get_info(handle(), ZX_INFO_PROCESS, &info,
sizeof(info), NULL, NULL);
if (status == ZX_OK) {
FXL_LOG(INFO) << "Process exited with code " << info.return_code;
return info.return_code;
} else {
FXL_LOG(ERROR) << "Error getting process exit code: "
<< util::ZxErrorString(status);
return -1;
}
}
const util::dsoinfo_t* Process::GetExecDso() {
return dso_get_main_exec(dsos_);
}
util::dsoinfo_t* Process::LookupDso(zx_vaddr_t pc) const {
return util::dso_lookup(dsos_, pc);
}
} // namespace debugserver