blob: e4e7540daab91fb3174b7dadd9e23c7ce22a19b2 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "ThreadDb.h"
#include "GdbServer.h"
#include "Task.h"
#include "ThreadGroup.h"
#include "core.h"
#include "log.h"
extern "C" {
// The proc_service/thread_db library has a very weird API. It
// requires the user of the library to provide certain functions which
// it links to (rather than, say, having the library user supply a
// struct of function pointers). We have to ensure that these
// functions have C linkage, so that libthread_db can find them.
#include "proc_service.h"
}
#include <dlfcn.h>
#include <linux/elf.h>
#define LIBRARY_NAME "libthread_db.so.1"
// Needed for the logging API.
using namespace rr;
ps_err_e ps_pglobal_lookup(struct ps_prochandle* h, const char*,
const char* symbol, psaddr_t* sym_addr) {
rr::remote_ptr<void> addr;
if (!h->db->query_symbol(symbol, &addr)) {
LOG(debug) << "ps_pglobal_lookup " << symbol << " failed";
return PS_NOSYM;
}
*sym_addr = reinterpret_cast<psaddr_t>(addr.as_int());
LOG(debug) << "ps_pglobal_lookup " << symbol << " OK";
return PS_OK;
}
ps_err_e ps_pdread(struct ps_prochandle* h, psaddr_t addr, void* buffer,
size_t len) {
if (!h->thread_group) {
FATAL() << "unexpected ps_pdread call with uninitialized thread_group";
}
bool ok = true;
uintptr_t uaddr = reinterpret_cast<uintptr_t>(addr);
// We need any task associated with the thread group. Here we assume
// that all the tasks in the thread group share VM, which is enforced
// by clone(2).
rr::Task* task = h->thread_group->first_running_task();
if (!task) {
return PS_ERR;
}
task->read_bytes_helper(uaddr, len, buffer, &ok);
LOG(debug) << "ps_pdread " << ok;
return ok ? PS_OK : PS_ERR;
}
ps_err_e ps_pdwrite(struct ps_prochandle*, psaddr_t, const void*, size_t) {
FATAL() << "ps_pdwrite not implemented";
return PS_ERR;
}
ps_err_e ps_lgetregs(struct ps_prochandle* h, lwpid_t rec_tid,
prgregset_t result) {
if (!h->thread_group) {
FATAL() << "unexpected ps_lgetregs call with uninitialized thread_group";
}
rr::Task* task = h->thread_group->session()->find_task(rec_tid);
DEBUG_ASSERT(task != nullptr);
NativeArch::user_regs_struct regs = task->regs().get_ptrace();
memcpy(result, static_cast<void*>(&regs), sizeof(regs));
LOG(debug) << "ps_lgetregs OK";
return PS_OK;
}
ps_err_e ps_lsetregs(struct ps_prochandle*, lwpid_t, const prgregset_t) {
FATAL() << "ps_lsetregs not implemented";
return PS_ERR;
}
ps_err_e ps_lgetfpregs(struct ps_prochandle*, lwpid_t, prfpregset_t*) {
FATAL() << "ps_lgetfpregs not implemented";
return PS_ERR;
}
ps_err_e ps_lsetfpregs(struct ps_prochandle*, lwpid_t, const prfpregset_t*) {
FATAL() << "ps_lsetfpregs not implemented";
return PS_ERR;
}
pid_t ps_getpid(struct ps_prochandle* h) {
LOG(debug) << "ps_getpid " << h->tgid;
return h->tgid;
}
static const int _REG_FS = 25;
static const int _REG_GS = 26;
ps_err_e ps_get_thread_area(const struct ps_prochandle* h, lwpid_t rec_tid,
int val, psaddr_t* base) {
if (!h->thread_group) {
FATAL()
<< "unexpected ps_get_thread_area call with uninitialized thread_group";
}
rr::Task* task = h->thread_group->session()->find_task(rec_tid);
DEBUG_ASSERT(task != nullptr);
if (task->arch() == rr::x86) {
unsigned int uval = static_cast<unsigned int>(val);
for (auto& area : task->thread_areas()) {
if (area.entry_number == uval) {
uintptr_t result = static_cast<uintptr_t>(area.base_addr);
*base = reinterpret_cast<psaddr_t>(result);
return PS_OK;
}
}
LOG(debug) << "ps_get_thread_area 32 failed";
return PS_ERR;
} else if (task->arch() == rr::x86_64) {
uintptr_t result;
switch (val) {
case _REG_FS:
result = task->regs().fs_base();
break;
case _REG_GS:
result = task->regs().gs_base();
break;
default:
LOG(debug) << "ps_get_thread_area PS_BADADDR";
return PS_BADADDR;
}
*base = reinterpret_cast<psaddr_t>(result);
return PS_OK;
} else if (task->arch() == aarch64) {
uintptr_t result;
if (!task->read_aarch64_tls_register(&result)) {
LOG(error) << "Task was dead";
return PS_ERR;
}
*base = reinterpret_cast<psaddr_t>(result - val);
return PS_OK;
} else {
LOG(error) << "Unknown architecture in ThreadDb";
return PS_ERR;
}
}
rr::ThreadDb::ThreadDb(pid_t tgid)
: internal_handle(nullptr),
thread_db_library(nullptr),
td_ta_delete_fn(nullptr),
td_thr_tls_get_addr_fn(nullptr),
td_ta_map_lwp2thr_fn(nullptr) {
prochandle.thread_group = nullptr;
prochandle.db = this;
prochandle.tgid = tgid;
}
rr::ThreadDb::~ThreadDb() {
if (internal_handle) {
td_ta_delete_fn(internal_handle);
}
if (thread_db_library) {
dlclose(thread_db_library);
}
}
const std::set<std::string> rr::ThreadDb::get_symbols_and_clear_map(
ThreadGroup* thread_group) {
// If we think the symbol locations might have changed, then we
// probably need to recreate the handle.
if (internal_handle) {
td_ta_delete_fn(internal_handle);
internal_handle = nullptr;
}
prochandle.thread_group = thread_group;
symbols.clear();
load_library();
prochandle.thread_group = nullptr;
return symbol_names;
}
void rr::ThreadDb::register_symbol(const std::string& name,
remote_ptr<void> address) {
LOG(debug) << "register_symbol " << name;
symbols[name] = address;
}
bool rr::ThreadDb::query_symbol(const char* name, remote_ptr<void>* address) {
auto it = symbols.find(name);
if (it == symbols.end()) {
return false;
}
*address = it->second;
return true;
}
bool rr::ThreadDb::get_tls_address(ThreadGroup* thread_group, pid_t rec_tid,
size_t offset, remote_ptr<void> load_module,
remote_ptr<void>* result) {
prochandle.thread_group = thread_group;
if (!initialize()) {
prochandle.thread_group = nullptr;
return false;
}
td_thrhandle_t th;
if (td_ta_map_lwp2thr_fn(internal_handle, rec_tid, &th) != TD_OK) {
prochandle.thread_group = nullptr;
return false;
}
psaddr_t load_module_addr = reinterpret_cast<psaddr_t>(load_module.as_int());
psaddr_t addr;
if (td_thr_tls_get_addr_fn(&th, load_module_addr, offset, &addr) != TD_OK) {
prochandle.thread_group = nullptr;
return false;
}
prochandle.thread_group = nullptr;
*result = remote_ptr<void>(reinterpret_cast<uintptr_t>(addr));
return true;
}
bool rr::ThreadDb::initialize() {
if (internal_handle) {
return true;
}
if (!load_library()) {
return false;
}
if (!td_ta_new_fn || td_ta_new_fn(&prochandle, &internal_handle) != TD_OK) {
LOG(debug) << "initialize td_ta_new_fn failed";
return false;
}
LOG(debug) << "initialize OK";
return true;
}
bool rr::ThreadDb::load_library() {
if (thread_db_library) {
LOG(debug) << "load_library already loaded: " << loaded;
return loaded;
}
thread_db_library = dlopen(LIBRARY_NAME, RTLD_NOW);
if (!thread_db_library) {
LOG(debug) << "load_library dlopen failed";
return false;
}
decltype(td_symbol_list)* td_symbol_list_fn;
#define FIND_FUNCTION(Name) \
do { \
Name##_fn = (decltype(Name)*)(dlsym(thread_db_library, #Name)); \
if (!Name##_fn) { \
LOG(debug) << "load_library failed to find " << #Name; \
return false; \
} \
} while (0)
FIND_FUNCTION(td_thr_tls_get_addr);
FIND_FUNCTION(td_ta_delete);
FIND_FUNCTION(td_symbol_list);
FIND_FUNCTION(td_ta_new);
FIND_FUNCTION(td_ta_map_lwp2thr);
#undef FIND_FUNCTION
for (const char** syms = td_symbol_list_fn(); *syms; ++syms) {
symbol_names.insert(*syms);
}
// Good to go.
loaded = true;
LOG(debug) << "load_library OK";
return true;
}