blob: 2e233196c3971d3368069072aeb1ffa17d22f43f [file] [log] [blame]
/*
* Copyright (c) 2016 GitHub, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cxxabi.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "bcc_elf.h"
#include "bcc_perf_map.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
#include "syms.h"
#include "vendor/tinyformat.hpp"
ino_t ProcStat::getinode_() {
struct stat s;
return (!stat(procfs_.c_str(), &s)) ? s.st_ino : -1;
}
ProcStat::ProcStat(int pid)
: procfs_(tfm::format("/proc/%d/exe", pid)), inode_(getinode_()) {}
void KSyms::_add_symbol(const char *symname, uint64_t addr, void *p) {
KSyms *ks = static_cast<KSyms *>(p);
ks->syms_.emplace_back(symname, addr);
}
void KSyms::refresh() {
if (syms_.empty()) {
bcc_procutils_each_ksym(_add_symbol, this);
std::sort(syms_.begin(), syms_.end());
}
}
bool KSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
refresh();
if (syms_.empty()) {
sym->name = nullptr;
sym->demangle_name = nullptr;
sym->module = nullptr;
sym->offset = 0x0;
return false;
}
auto it = std::upper_bound(syms_.begin(), syms_.end(), Symbol("", addr)) - 1;
sym->name = (*it).name.c_str();
sym->demangle_name = sym->name;
sym->module = "kernel";
sym->offset = addr - (*it).addr;
return true;
}
bool KSyms::resolve_name(const char *_unused, const char *name,
uint64_t *addr) {
refresh();
if (syms_.size() != symnames_.size()) {
symnames_.clear();
for (Symbol &sym : syms_) {
symnames_[sym.name] = sym.addr;
}
}
auto it = symnames_.find(name);
if (it == symnames_.end())
return false;
*addr = it->second;
return true;
}
ProcSyms::ProcSyms(int pid) : pid_(pid), procstat_(pid) { load_modules(); }
bool ProcSyms::load_modules() {
return bcc_procutils_each_module(pid_, _add_module, this) == 0;
}
void ProcSyms::refresh() {
modules_.clear();
load_modules();
procstat_.reset();
}
int ProcSyms::_add_module(const char *modname, uint64_t start, uint64_t end,
void *payload) {
ProcSyms *ps = static_cast<ProcSyms *>(payload);
ps->modules_.emplace_back(modname, start, end);
return 0;
}
bool ProcSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
if (procstat_.is_stale())
refresh();
sym->module = nullptr;
sym->name = nullptr;
sym->demangle_name = nullptr;
sym->offset = 0x0;
const char *original_module = nullptr;
for (Module &mod : modules_) {
if (addr >= mod.start_ && addr < mod.end_) {
bool res = mod.find_addr(addr, sym);
if (sym->name) {
sym->demangle_name = abi::__cxa_demangle(sym->name, nullptr, nullptr, nullptr);
if (!sym->demangle_name)
sym->demangle_name = sym->name;
}
// If we have a match, return right away. But if we don't have a match in
// this module, we might have a match in the perf map (even though the
// module itself doesn't have symbols). Wait until we see the perf map if
// any, but keep the original module name for reporting.
if (res) {
// If we have already seen this module, report the original name rather
// than the perf map name:
if (original_module)
sym->module = original_module;
return res;
} else {
// Record the module to which this symbol belongs, so that even if it's
// later found using a perf map, we still report the right module name.
original_module = mod.name_.c_str();
}
}
}
return false;
}
bool ProcSyms::resolve_name(const char *module, const char *name,
uint64_t *addr) {
if (procstat_.is_stale())
refresh();
for (Module &mod : modules_) {
if (mod.name_ == module)
return mod.find_name(name, addr);
}
return false;
}
ProcSyms::Module::Module(const char *name, uint64_t start, uint64_t end)
: name_(name), start_(start), end_(end) {
is_so_ = bcc_elf_is_shared_obj(name) == 1;
}
int ProcSyms::Module::_add_symbol(const char *symname, uint64_t start,
uint64_t end, int flags, void *p) {
Module *m = static_cast<Module *>(p);
auto res = m->symnames_.emplace(symname);
m->syms_.emplace_back(&*(res.first), start, end, flags);
return 0;
}
bool ProcSyms::Module::is_perf_map() const {
return strstr(name_.c_str(), ".map") != nullptr;
}
void ProcSyms::Module::load_sym_table() {
if (syms_.size())
return;
if (is_perf_map())
bcc_perf_map_foreach_sym(name_.c_str(), _add_symbol, this);
else
bcc_elf_foreach_sym(name_.c_str(), _add_symbol, this);
std::sort(syms_.begin(), syms_.end());
}
bool ProcSyms::Module::find_name(const char *symname, uint64_t *addr) {
load_sym_table();
for (Symbol &s : syms_) {
if (*(s.name) == symname) {
*addr = is_so() ? start_ + s.start : s.start;
return true;
}
}
return false;
}
bool ProcSyms::Module::find_addr(uint64_t addr, struct bcc_symbol *sym) {
uint64_t offset = is_so() ? (addr - start_) : addr;
load_sym_table();
sym->module = name_.c_str();
sym->offset = offset;
auto it = std::upper_bound(syms_.begin(), syms_.end(), Symbol(nullptr, offset, 0));
if (it != syms_.begin())
--it;
else
it = syms_.end();
if (it != syms_.end()
&& offset >= it->start && offset < it->start + it->size) {
sym->name = it->name->c_str();
sym->offset = (offset - it->start);
return true;
}
return false;
}
extern "C" {
void *bcc_symcache_new(int pid) {
if (pid < 0)
return static_cast<void *>(new KSyms());
return static_cast<void *>(new ProcSyms(pid));
}
void bcc_free_symcache(void *symcache, int pid) {
if (pid < 0)
delete static_cast<KSyms*>(symcache);
else
delete static_cast<ProcSyms*>(symcache);
}
int bcc_symcache_resolve(void *resolver, uint64_t addr,
struct bcc_symbol *sym) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
return cache->resolve_addr(addr, sym) ? 0 : -1;
}
int bcc_symcache_resolve_name(void *resolver, const char *module,
const char *name, uint64_t *addr) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
return cache->resolve_name(module, name, addr) ? 0 : -1;
}
void bcc_symcache_refresh(void *resolver) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
cache->refresh();
}
struct mod_st {
const char *name;
uint64_t start;
};
static int _find_module(const char *modname, uint64_t start, uint64_t end,
void *p) {
struct mod_st *mod = (struct mod_st *)p;
if (!strcmp(modname, mod->name)) {
mod->start = start;
return -1;
}
return 0;
}
int bcc_resolve_global_addr(int pid, const char *module, const uint64_t address,
uint64_t *global) {
struct mod_st mod = {module, 0x0};
if (bcc_procutils_each_module(pid, _find_module, &mod) < 0 ||
mod.start == 0x0)
return -1;
*global = mod.start + address;
return 0;
}
static int _find_sym(const char *symname, uint64_t addr, uint64_t end,
int flags, void *payload) {
struct bcc_symbol *sym = (struct bcc_symbol *)payload;
// TODO: check for actual function symbol in flags
if (!strcmp(sym->name, symname)) {
sym->offset = addr;
return -1;
}
return 0;
}
int bcc_find_symbol_addr(struct bcc_symbol *sym) {
return bcc_elf_foreach_sym(sym->module, _find_sym, sym);
}
struct sym_search_t {
struct bcc_symbol *syms;
int start;
int requested;
int *actual;
};
// see <elf.h>
#define ELF_TYPE_IS_FUNCTION(flags) (((flags) & 0xf) == 2)
static int _list_sym(const char *symname, uint64_t addr, uint64_t end,
int flags, void *payload) {
if (!ELF_TYPE_IS_FUNCTION(flags) || addr == 0)
return 0;
SYM_CB cb = (SYM_CB) payload;
return cb(symname, addr);
}
int bcc_foreach_symbol(const char *module, SYM_CB cb) {
if (module == 0 || cb == 0)
return -1;
return bcc_elf_foreach_sym(module, _list_sym, (void *)cb);
}
int bcc_resolve_symname(const char *module, const char *symname,
const uint64_t addr, int pid, struct bcc_symbol *sym) {
uint64_t load_addr;
sym->module = NULL;
sym->name = NULL;
sym->offset = 0x0;
if (module == NULL)
return -1;
if (strchr(module, '/')) {
sym->module = strdup(module);
} else {
sym->module = bcc_procutils_which_so(module, pid);
}
if (sym->module == NULL)
return -1;
if (bcc_elf_loadaddr(sym->module, &load_addr) < 0) {
sym->module = NULL;
return -1;
}
sym->name = symname;
sym->offset = addr;
if (sym->name && sym->offset == 0x0) {
if (bcc_find_symbol_addr(sym) < 0)
return -1;
}
if (sym->offset == 0x0)
return -1;
sym->offset = (sym->offset - load_addr);
return 0;
}
}