blob: 0263391c34875289a30981f4bfaf87a64a00dbcc [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 "src/developer/debug/debug_agent/process_info.h"
// clang-format off
// Included early because of conflicts.
#include "src/lib/elflib/elflib.h"
// clang-format on
#include <inttypes.h>
#include <lib/zx/thread.h>
#include <link.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <algorithm>
#include <iterator>
#include <map>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/object_provider.h"
#include <lib/syslog/cpp/macros.h>
namespace debug_agent {
namespace {
zx_status_t WalkModules(const zx::process& process, uint64_t dl_debug_addr,
std::function<bool(const zx::process&, uint64_t, uint64_t)> cb) {
size_t num_read = 0;
uint64_t lmap = 0;
zx_status_t status =
process.read_memory(dl_debug_addr + offsetof(r_debug, r_map), &lmap, sizeof(lmap), &num_read);
if (status != ZX_OK)
return status;
size_t module_count = 0;
// Walk the linked list.
constexpr size_t kMaxObjects = 512; // Sanity threshold.
while (lmap != 0) {
if (module_count++ >= kMaxObjects)
return ZX_ERR_BAD_STATE;
uint64_t base;
if (process.read_memory(lmap + offsetof(link_map, l_addr), &base, sizeof(base), &num_read) !=
ZX_OK)
break;
uint64_t next;
if (process.read_memory(lmap + offsetof(link_map, l_next), &next, sizeof(next), &num_read) !=
ZX_OK)
break;
if (!cb(process, base, lmap))
break;
lmap = next;
}
return ZX_OK;
}
debug_ipc::ThreadRecord::BlockedReason ThreadStateBlockedReasonToEnum(uint32_t state) {
FX_DCHECK(ZX_THREAD_STATE_BASIC(state) == ZX_THREAD_STATE_BLOCKED);
switch (state) {
case ZX_THREAD_STATE_BLOCKED_EXCEPTION:
return debug_ipc::ThreadRecord::BlockedReason::kException;
case ZX_THREAD_STATE_BLOCKED_SLEEPING:
return debug_ipc::ThreadRecord::BlockedReason::kSleeping;
case ZX_THREAD_STATE_BLOCKED_FUTEX:
return debug_ipc::ThreadRecord::BlockedReason::kFutex;
case ZX_THREAD_STATE_BLOCKED_PORT:
return debug_ipc::ThreadRecord::BlockedReason::kPort;
case ZX_THREAD_STATE_BLOCKED_CHANNEL:
return debug_ipc::ThreadRecord::BlockedReason::kChannel;
case ZX_THREAD_STATE_BLOCKED_WAIT_ONE:
return debug_ipc::ThreadRecord::BlockedReason::kWaitOne;
case ZX_THREAD_STATE_BLOCKED_WAIT_MANY:
return debug_ipc::ThreadRecord::BlockedReason::kWaitMany;
case ZX_THREAD_STATE_BLOCKED_INTERRUPT:
return debug_ipc::ThreadRecord::BlockedReason::kInterrupt;
case ZX_THREAD_STATE_BLOCKED_PAGER:
return debug_ipc::ThreadRecord::BlockedReason::kPager;
default:
FX_NOTREACHED();
return debug_ipc::ThreadRecord::BlockedReason::kNotBlocked;
}
}
// Reads a null-terminated string from the given address of the given process.
zx_status_t ReadNullTerminatedString(const zx::process& process, zx_vaddr_t vaddr,
std::string* dest) {
// Max size of string we'll load as a sanity check.
constexpr size_t kMaxString = 32768;
dest->clear();
constexpr size_t kBlockSize = 256;
char block[kBlockSize];
while (dest->size() < kMaxString) {
size_t num_read = 0;
zx_status_t status = process.read_memory(vaddr, block, kBlockSize, &num_read);
if (status != ZX_OK)
return status;
for (size_t i = 0; i < num_read; i++) {
if (block[i] == 0)
return ZX_OK;
dest->push_back(block[i]);
}
if (num_read < kBlockSize)
return ZX_OK; // Partial read: hit the mapped memory boundary.
vaddr += kBlockSize;
}
return ZX_OK;
}
} // namespace
zx_status_t GetModulesForProcess(const zx::process& process, uint64_t dl_debug_addr,
std::vector<debug_ipc::Module>* modules) {
return WalkModules(
process, dl_debug_addr, [modules](const zx::process& process, uint64_t base, uint64_t lmap) {
debug_ipc::Module module;
module.base = base;
module.debug_address = lmap;
uint64_t str_addr;
size_t num_read;
if (process.read_memory(lmap + offsetof(link_map, l_name), &str_addr, sizeof(str_addr),
&num_read) != ZX_OK)
return false;
if (ReadNullTerminatedString(process, str_addr, &module.name) != ZX_OK)
return false;
auto elf = elflib::ElfLib::Create([&process, base = module.base](
uint64_t offset, std::vector<uint8_t>* buf) {
size_t num_read = 0;
if (process.read_memory(base + offset, buf->data(), buf->size(), &num_read) != ZX_OK) {
return false;
}
return num_read == buf->size();
});
if (elf) {
module.build_id = elf->GetGNUBuildID();
}
modules->push_back(std::move(module));
return true;
});
}
debug_ipc::ThreadRecord::State ThreadStateToEnums(
uint32_t state, debug_ipc::ThreadRecord::BlockedReason* blocked_reason) {
struct Mapping {
uint32_t int_state;
debug_ipc::ThreadRecord::State enum_state;
};
static const Mapping mappings[] = {
{ZX_THREAD_STATE_NEW, debug_ipc::ThreadRecord::State::kNew},
{ZX_THREAD_STATE_RUNNING, debug_ipc::ThreadRecord::State::kRunning},
{ZX_THREAD_STATE_SUSPENDED, debug_ipc::ThreadRecord::State::kSuspended},
{ZX_THREAD_STATE_BLOCKED, debug_ipc::ThreadRecord::State::kBlocked},
{ZX_THREAD_STATE_DYING, debug_ipc::ThreadRecord::State::kDying},
{ZX_THREAD_STATE_DEAD, debug_ipc::ThreadRecord::State::kDead}};
const uint32_t basic_state = ZX_THREAD_STATE_BASIC(state);
*blocked_reason = debug_ipc::ThreadRecord::BlockedReason::kNotBlocked;
for (const Mapping& mapping : mappings) {
if (mapping.int_state == basic_state) {
if (mapping.enum_state == debug_ipc::ThreadRecord::State::kBlocked) {
*blocked_reason = ThreadStateBlockedReasonToEnum(state);
}
return mapping.enum_state;
}
}
FX_NOTREACHED();
return debug_ipc::ThreadRecord::State::kDead;
}
} // namespace debug_agent