blob: 3c41ebb9bfb907d8029ae8f39ade4d088169a4fb [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/debug_agent/debugged_process.h"
#include <inttypes.h>
#include <zircon/syscalls/exception.h>
#include <utility>
#include "garnet/bin/debug_agent/debug_agent.h"
#include "garnet/bin/debug_agent/debugged_thread.h"
#include "garnet/bin/debug_agent/debugged_thread.h"
#include "garnet/bin/debug_agent/object_util.h"
#include "garnet/bin/debug_agent/process_breakpoint.h"
#include "garnet/bin/debug_agent/process_info.h"
#include "garnet/bin/debug_agent/process_memory_accessor.h"
#include "garnet/lib/debug_ipc/agent_protocol.h"
#include "garnet/lib/debug_ipc/helper/message_loop_zircon.h"
#include "garnet/lib/debug_ipc/message_reader.h"
#include "garnet/lib/debug_ipc/message_writer.h"
#include "garnet/public/lib/fxl/logging.h"
namespace debug_agent {
DebuggedProcess::DebuggedProcess(DebugAgent* debug_agent, zx_koid_t koid,
zx::process proc)
: debug_agent_(debug_agent), koid_(koid), process_(std::move(proc)) {}
DebuggedProcess::~DebuggedProcess() = default;
bool DebuggedProcess::Init() {
debug_ipc::MessageLoopZircon* loop = debug_ipc::MessageLoopZircon::Current();
FXL_DCHECK(loop); // Loop must be created on this thread first.
// Register for debug exceptions.
process_watch_handle_ =
loop->WatchProcessExceptions(process_.get(), koid_, this);
return process_watch_handle_.watching();
}
void DebuggedProcess::OnPause(const debug_ipc::PauseRequest& request) {
if (request.thread_koid) {
DebuggedThread* thread = GetThread(request.thread_koid);
if (thread) thread->Pause();
// Could be not found if there is a race between the thread exiting and
// the client sending the request.
} else {
// 0 thread ID means resume all in process.
for (const auto& pair : threads_) pair.second->Pause();
}
}
void DebuggedProcess::OnResume(const debug_ipc::ResumeRequest& request) {
if (request.thread_koid) {
DebuggedThread* thread = GetThread(request.thread_koid);
if (thread) thread->Resume(request.how);
// Could be not found if there is a race between the thread exiting and
// the client sending the request.
} else {
// 0 thread ID means resume all in process.
for (const auto& pair : threads_) pair.second->Resume(request.how);
}
}
void DebuggedProcess::OnReadMemory(const debug_ipc::ReadMemoryRequest& request,
debug_ipc::ReadMemoryReply* reply) {
// TODO(brettw) break into blocks if a portion of the memory range is mapped
// but a portion isn't. Currently this assumes the entire range is in one
// block.
debug_ipc::MemoryBlock block;
block.address = request.address;
block.size = request.size;
block.data.resize(request.size);
size_t bytes_read = 0;
if (process_.read_memory(request.address, &block.data[0], block.size,
&bytes_read) == ZX_OK &&
bytes_read == block.size) {
block.valid = true;
} else {
block.valid = false;
block.data.resize(0);
}
reply->blocks.emplace_back(std::move(block));
}
void DebuggedProcess::OnKill(const debug_ipc::KillRequest& request,
debug_ipc::KillReply* reply) {
reply->status = process_.kill();
}
DebuggedThread* DebuggedProcess::GetThread(zx_koid_t thread_koid) {
auto found_thread = threads_.find(thread_koid);
if (found_thread == threads_.end()) return nullptr;
return found_thread->second.get();
}
void DebuggedProcess::PopulateCurrentThreads() {
for (zx_koid_t koid :
GetChildKoids(process_.get(), ZX_INFO_PROCESS_THREADS)) {
FXL_DCHECK(threads_.find(koid) == threads_.end());
zx_handle_t handle;
if (zx_object_get_child(process_.get(), koid, ZX_RIGHT_SAME_RIGHTS,
&handle) == ZX_OK) {
auto added =
threads_.emplace(koid, std::make_unique<DebuggedThread>(
this, zx::thread(handle), koid, true));
added.first->second->SendThreadNotification();
}
}
}
ProcessBreakpoint* DebuggedProcess::FindProcessBreakpointForAddr(
uint64_t address) {
auto found = breakpoints_.find(address);
if (found == breakpoints_.end()) return nullptr;
return found->second.get();
}
zx_status_t DebuggedProcess::RegisterBreakpoint(Breakpoint* bp,
uint64_t address) {
auto found = breakpoints_.find(address);
if (found == breakpoints_.end()) {
auto process_breakpoint =
std::make_unique<ProcessBreakpoint>(bp, this, koid_, address);
zx_status_t status = process_breakpoint->Init();
if (status != ZX_OK) return status;
breakpoints_[address] = std::move(process_breakpoint);
} else {
found->second->RegisterBreakpoint(bp);
}
return ZX_OK;
}
void DebuggedProcess::UnregisterBreakpoint(Breakpoint* bp, uint64_t address) {
auto found = breakpoints_.find(address);
if (found == breakpoints_.end()) {
FXL_NOTREACHED(); // Should always be found.
return;
}
bool still_used = found->second->UnregisterBreakpoint(bp);
if (!still_used) breakpoints_.erase(found);
}
void DebuggedProcess::OnProcessTerminated(zx_koid_t process_koid) {
debug_ipc::NotifyProcess notify;
notify.process_koid = process_koid;
zx_info_process info;
GetProcessInfo(process_.get(), &info);
notify.return_code = info.return_code;
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyProcess(notify, &writer);
debug_agent_->stream()->Write(writer.MessageComplete());
debug_agent_->RemoveDebuggedProcess(process_koid);
// "THIS" IS NOW DELETED.
}
void DebuggedProcess::OnThreadStarting(zx_koid_t process_koid,
zx_koid_t thread_koid) {
zx::thread thread = ThreadForKoid(process_.get(), thread_koid);
// The thread will currently be in a suspended state, resume it.
thread.resume(ZX_RESUME_EXCEPTION);
FXL_DCHECK(threads_.find(thread_koid) == threads_.end());
auto added = threads_.emplace(
thread_koid, std::make_unique<DebuggedThread>(this, std::move(thread),
thread_koid, true));
// Notify the client.
added.first->second->SendThreadNotification();
}
void DebuggedProcess::OnThreadExiting(zx_koid_t process_koid,
zx_koid_t thread_koid) {
// Clean up our DebuggedThread object.
FXL_DCHECK(threads_.find(thread_koid) != threads_.end());
threads_.erase(thread_koid);
// Notify the client. Can't call FillThreadRecord since the thread doesn't
// exist any more.
debug_ipc::NotifyThread notify;
notify.process_koid = process_koid;
notify.record.koid = thread_koid;
notify.record.state = debug_ipc::ThreadRecord::State::kDead;
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyThread(debug_ipc::MsgHeader::Type::kNotifyThreadExiting,
notify, &writer);
debug_agent_->stream()->Write(writer.MessageComplete());
}
void DebuggedProcess::OnException(zx_koid_t process_koid, zx_koid_t thread_koid,
uint32_t type) {
DebuggedThread* thread = GetThread(thread_koid);
if (thread) {
thread->OnException(type);
} else {
fprintf(stderr,
"Exception for thread %" PRIu64 " which we don't know about.\n",
thread_koid);
}
}
void DebuggedProcess::OnAddressSpace(
const debug_ipc::AddressSpaceRequest& request,
debug_ipc::AddressSpaceReply* reply) {
const size_t kRegionsCountGuess = 64u;
const size_t kNewRegionsCountGuess = 4u;
size_t count_guess = kRegionsCountGuess;
std::vector<zx_info_maps_t> map;
size_t actual;
size_t avail;
while (true) {
map.resize(count_guess);
zx_status_t status =
zx_object_get_info(process_.get(), ZX_INFO_PROCESS_MAPS, &map[0],
sizeof(zx_info_maps) * map.size(), &actual, &avail);
if (status != ZX_OK) {
fprintf(stderr, "error %d for zx_object_get_info\n", status);
return;
} else if (actual == avail) {
break;
}
count_guess = avail + kNewRegionsCountGuess;
}
map.resize(actual);
if (request.address != 0u) {
for (const auto& entry : map) {
if (request.address < entry.base)
continue;
if (request.address <= (entry.base + entry.size)) {
reply->map.push_back({entry.name, entry.base, entry.size, entry.depth});
}
}
return;
}
reply->map.resize(actual);
size_t ix = 0;
for (const auto& entry : map) {
reply->map[ix].name = entry.name;
reply->map[ix].base = entry.base;
reply->map[ix].size = entry.size;
reply->map[ix].depth = entry.depth;
++ix;
}
}
zx_status_t DebuggedProcess::ReadProcessMemory(uintptr_t address, void* buffer,
size_t len, size_t* actual) {
return process_.read_memory(address, buffer, len, actual);
}
zx_status_t DebuggedProcess::WriteProcessMemory(uintptr_t address,
const void* buffer, size_t len,
size_t* actual) {
return process_.write_memory(address, buffer, len, actual);
}
} // namespace debug_agent