/*
|
* 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 <cstring>
|
#include <fcntl.h>
|
#include <linux/elf.h>
|
#include <string.h>
|
#include <sys/stat.h>
|
#include <sys/types.h>
|
#include <unistd.h>
|
#include <cstdio>
|
|
#include "bcc_elf.h"
|
#include "bcc_perf_map.h"
|
#include "bcc_proc.h"
|
#include "bcc_syms.h"
|
#include "common.h"
|
#include "vendor/tinyformat.hpp"
|
|
#include "syms.h"
|
|
ino_t ProcStat::getinode_() {
|
struct stat s;
|
return (!stat(procfs_.c_str(), &s)) ? s.st_ino : -1;
|
}
|
|
bool ProcStat::is_stale() {
|
ino_t cur_inode = getinode_();
|
return (cur_inode > 0) && (cur_inode != inode_);
|
}
|
|
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, bool demangle) {
|
refresh();
|
|
std::vector<Symbol>::iterator it;
|
|
if (syms_.empty())
|
goto unknown_symbol;
|
|
it = std::upper_bound(syms_.begin(), syms_.end(), Symbol("", addr));
|
if (it != syms_.begin()) {
|
it--;
|
sym->name = (*it).name.c_str();
|
if (demangle)
|
sym->demangle_name = sym->name;
|
sym->module = "kernel";
|
sym->offset = addr - (*it).addr;
|
return true;
|
}
|
|
unknown_symbol:
|
memset(sym, 0, sizeof(struct bcc_symbol));
|
return false;
|
}
|
|
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, struct bcc_symbol_option *option)
|
: pid_(pid), procstat_(pid), mount_ns_instance_(new ProcMountNS(pid_)) {
|
if (option)
|
std::memcpy(&symbol_option_, option, sizeof(bcc_symbol_option));
|
else
|
symbol_option_ = {
|
.use_debug_file = 1,
|
.check_debug_file_crc = 1,
|
.use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC)
|
};
|
load_modules();
|
}
|
|
int ProcSyms::_add_load_sections(uint64_t v_addr, uint64_t mem_sz,
|
uint64_t file_offset, void *payload) {
|
auto module = static_cast<Module *>(payload);
|
module->ranges_.emplace_back(v_addr, v_addr + mem_sz, file_offset);
|
return 0;
|
}
|
|
void ProcSyms::load_exe() {
|
std::string exe = ebpf::get_pid_exe(pid_);
|
Module module(exe.c_str(), mount_ns_instance_.get(), &symbol_option_);
|
|
if (module.type_ != ModuleType::EXEC)
|
return;
|
|
ProcMountNSGuard g(mount_ns_instance_.get());
|
|
bcc_elf_foreach_load_section(exe.c_str(), &_add_load_sections, &module);
|
|
if (!module.ranges_.empty())
|
modules_.emplace_back(std::move(module));
|
}
|
|
void ProcSyms::load_modules() {
|
load_exe();
|
bcc_procutils_each_module(pid_, _add_module, this);
|
}
|
|
void ProcSyms::refresh() {
|
modules_.clear();
|
mount_ns_instance_.reset(new ProcMountNS(pid_));
|
load_modules();
|
procstat_.reset();
|
}
|
|
int ProcSyms::_add_module(const char *modname, uint64_t start, uint64_t end,
|
uint64_t offset, bool check_mount_ns, void *payload) {
|
ProcSyms *ps = static_cast<ProcSyms *>(payload);
|
auto it = std::find_if(
|
ps->modules_.begin(), ps->modules_.end(),
|
[=](const ProcSyms::Module &m) { return m.name_ == modname; });
|
if (it == ps->modules_.end()) {
|
auto module = Module(
|
modname, check_mount_ns ? ps->mount_ns_instance_.get() : nullptr,
|
&ps->symbol_option_);
|
|
// pid/maps doesn't account for file_offset of text within the ELF.
|
// It only gives the mmap offset. We need the real offset for symbol
|
// lookup.
|
if (module.type_ == ModuleType::SO) {
|
if (bcc_elf_get_text_scn_info(modname, &module.elf_so_addr_,
|
&module.elf_so_offset_) < 0) {
|
fprintf(stderr, "WARNING: Couldn't find .text section in %s\n", modname);
|
fprintf(stderr, "WARNING: BCC can't handle sym look ups for %s", modname);
|
}
|
}
|
|
if (!bcc_is_perf_map(modname) || module.type_ != ModuleType::UNKNOWN)
|
// Always add the module even if we can't read it, so that we could
|
// report correct module name. Unless it's a perf map that we only
|
// add readable ones.
|
it = ps->modules_.insert(ps->modules_.end(), std::move(module));
|
else
|
return 0;
|
}
|
it->ranges_.emplace_back(start, end, offset);
|
// perf-PID map is added last. We try both inside the Process's mount
|
// namespace + chroot, and in global /tmp. Make sure we only add one.
|
if (it->type_ == ModuleType::PERF_MAP)
|
return -1;
|
|
return 0;
|
}
|
|
bool ProcSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym,
|
bool demangle) {
|
if (procstat_.is_stale())
|
refresh();
|
|
memset(sym, 0, sizeof(struct bcc_symbol));
|
|
const char *original_module = nullptr;
|
uint64_t offset;
|
bool only_perf_map = false;
|
for (Module &mod : modules_) {
|
if (only_perf_map && (mod.type_ != ModuleType::PERF_MAP))
|
continue;
|
if (mod.contains(addr, offset)) {
|
if (mod.find_addr(offset, sym)) {
|
if (demangle) {
|
if (sym->name && (!strncmp(sym->name, "_Z", 2) || !strncmp(sym->name, "___Z", 4)))
|
sym->demangle_name =
|
abi::__cxa_demangle(sym->name, nullptr, nullptr, nullptr);
|
if (!sym->demangle_name)
|
sym->demangle_name = sym->name;
|
}
|
return true;
|
} else if (mod.type_ != ModuleType::PERF_MAP) {
|
// In this case, we found the address in the range of a module, but
|
// not able to find a symbol of that address in the module.
|
// Thus, we would try to find the address in perf map, and
|
// save the module's name in case we will need it later.
|
original_module = mod.name_.c_str();
|
only_perf_map = true;
|
}
|
}
|
}
|
// If we didn't find the symbol anywhere, the module name is probably
|
// set to be the perf map's name as it would be the last we tried.
|
// In this case, if we have found the address previously in a module,
|
// report the saved original module name instead.
|
if (original_module)
|
sym->module = original_module;
|
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, ProcMountNS *mount_ns,
|
struct bcc_symbol_option *option)
|
: name_(name),
|
loaded_(false),
|
mount_ns_(mount_ns),
|
symbol_option_(option),
|
type_(ModuleType::UNKNOWN) {
|
ProcMountNSGuard g(mount_ns_);
|
int elf_type = bcc_elf_get_type(name_.c_str());
|
// The Module is an ELF file
|
if (elf_type >= 0) {
|
if (elf_type == ET_EXEC)
|
type_ = ModuleType::EXEC;
|
else if (elf_type == ET_DYN)
|
type_ = ModuleType::SO;
|
return;
|
}
|
// Other symbol files
|
if (bcc_is_valid_perf_map(name_.c_str()) == 1)
|
type_ = ModuleType::PERF_MAP;
|
else if (bcc_elf_is_vdso(name_.c_str()) == 1)
|
type_ = ModuleType::VDSO;
|
|
// Will be stored later
|
elf_so_offset_ = 0;
|
elf_so_addr_ = 0;
|
}
|
|
int ProcSyms::Module::_add_symbol(const char *symname, uint64_t start,
|
uint64_t size, void *p) {
|
Module *m = static_cast<Module *>(p);
|
auto res = m->symnames_.emplace(symname);
|
m->syms_.emplace_back(&*(res.first), start, size);
|
return 0;
|
}
|
|
void ProcSyms::Module::load_sym_table() {
|
if (loaded_)
|
return;
|
loaded_ = true;
|
|
if (type_ == ModuleType::UNKNOWN)
|
return;
|
|
ProcMountNSGuard g(mount_ns_);
|
|
if (type_ == ModuleType::PERF_MAP)
|
bcc_perf_map_foreach_sym(name_.c_str(), _add_symbol, this);
|
if (type_ == ModuleType::EXEC || type_ == ModuleType::SO)
|
bcc_elf_foreach_sym(name_.c_str(), _add_symbol, symbol_option_, this);
|
if (type_ == ModuleType::VDSO)
|
bcc_elf_foreach_vdso_sym(_add_symbol, this);
|
|
std::sort(syms_.begin(), syms_.end());
|
}
|
|
bool ProcSyms::Module::contains(uint64_t addr, uint64_t &offset) const {
|
for (const auto &range : ranges_) {
|
if (addr >= range.start && addr < range.end) {
|
if (type_ == ModuleType::SO || type_ == ModuleType::VDSO) {
|
// Offset within the mmap
|
offset = addr - range.start + range.file_offset;
|
|
// Offset within the ELF for SO symbol lookup
|
offset += (elf_so_addr_ - elf_so_offset_);
|
} else {
|
offset = addr;
|
}
|
|
return true;
|
}
|
}
|
|
return false;
|
}
|
|
bool ProcSyms::Module::find_name(const char *symname, uint64_t *addr) {
|
load_sym_table();
|
|
for (Symbol &s : syms_) {
|
if (*(s.name) == symname) {
|
*addr = type_ == ModuleType::SO ? start() + s.start : s.start;
|
return true;
|
}
|
}
|
return false;
|
}
|
|
bool ProcSyms::Module::find_addr(uint64_t offset, struct bcc_symbol *sym) {
|
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())
|
return false;
|
|
// 'it' points to the symbol whose start address is strictly greater than
|
// the address we're looking for. Start stepping backwards as long as the
|
// current symbol is still below the desired address, and see if the end
|
// of the current symbol (start + size) is above the desired address. Once
|
// we have a matching symbol, return it. Note that simply looking at '--it'
|
// is not enough, because symbols can be nested. For example, we could be
|
// looking for offset 0x12 with the following symbols available:
|
// SYMBOL START SIZE END
|
// goo 0x0 0x6 0x0 + 0x6 = 0x6
|
// foo 0x6 0x10 0x6 + 0x10 = 0x16
|
// bar 0x8 0x4 0x8 + 0x4 = 0xc
|
// baz 0x16 0x10 0x16 + 0x10 = 0x26
|
// The upper_bound lookup will return baz, and then going one symbol back
|
// brings us to bar, which does not contain offset 0x12 and is nested inside
|
// foo. Going back one more symbol brings us to foo, which contains 0x12
|
// and is a match.
|
// However, we also don't want to walk through the entire symbol list for
|
// unknown / missing symbols. So we will break if we reach a function that
|
// doesn't cover the function immediately before 'it', which means it is
|
// not possibly a nested function containing the address we're looking for.
|
--it;
|
uint64_t limit = it->start;
|
for (; offset >= it->start; --it) {
|
if (offset < it->start + it->size) {
|
sym->name = it->name->c_str();
|
sym->offset = (offset - it->start);
|
return true;
|
}
|
if (limit > it->start + it->size)
|
break;
|
// But don't step beyond begin()!
|
if (it == syms_.begin())
|
break;
|
}
|
|
return false;
|
}
|
|
extern "C" {
|
|
void *bcc_symcache_new(int pid, struct bcc_symbol_option *option) {
|
if (pid < 0)
|
return static_cast<void *>(new KSyms());
|
return static_cast<void *>(new ProcSyms(pid, option));
|
}
|
|
void bcc_free_symcache(void *symcache, int pid) {
|
if (pid < 0)
|
delete static_cast<KSyms*>(symcache);
|
else
|
delete static_cast<ProcSyms*>(symcache);
|
}
|
|
void bcc_symbol_free_demangle_name(struct bcc_symbol *sym) {
|
if (sym->demangle_name && (sym->demangle_name != sym->name))
|
free(const_cast<char*>(sym->demangle_name));
|
}
|
|
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_no_demangle(void *resolver, uint64_t addr,
|
struct bcc_symbol *sym) {
|
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
|
return cache->resolve_addr(addr, sym, false) ? 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;
|
uint64_t file_offset;
|
};
|
|
static int _find_module(const char *modname, uint64_t start, uint64_t end,
|
uint64_t offset, bool, void *p) {
|
struct mod_st *mod = (struct mod_st *)p;
|
if (!strcmp(modname, mod->name)) {
|
mod->start = start;
|
mod->file_offset = offset;
|
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 - mod.file_offset + address;
|
return 0;
|
}
|
|
static int _sym_cb_wrapper(const char *symname, uint64_t addr, uint64_t,
|
void *payload) {
|
SYM_CB cb = (SYM_CB) payload;
|
return cb(symname, addr);
|
}
|
|
int bcc_foreach_function_symbol(const char *module, SYM_CB cb) {
|
if (module == 0 || cb == 0)
|
return -1;
|
|
static struct bcc_symbol_option default_option = {
|
.use_debug_file = 1,
|
.check_debug_file_crc = 1,
|
.use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC)
|
};
|
|
return bcc_elf_foreach_sym(
|
module, _sym_cb_wrapper, &default_option, (void *)cb);
|
}
|
|
static int _find_sym(const char *symname, uint64_t addr, uint64_t,
|
void *payload) {
|
struct bcc_symbol *sym = (struct bcc_symbol *)payload;
|
if (!strcmp(sym->name, symname)) {
|
sym->offset = addr;
|
return -1;
|
}
|
return 0;
|
}
|
|
struct load_addr_t {
|
uint64_t target_addr;
|
uint64_t binary_addr;
|
};
|
int _find_load(uint64_t v_addr, uint64_t mem_sz, uint64_t file_offset,
|
void *payload) {
|
struct load_addr_t *addr = static_cast<load_addr_t *>(payload);
|
if (addr->target_addr >= v_addr && addr->target_addr < (v_addr + mem_sz)) {
|
addr->binary_addr = addr->target_addr - v_addr + file_offset;
|
return -1;
|
}
|
return 0;
|
}
|
|
int bcc_resolve_symname(const char *module, const char *symname,
|
const uint64_t addr, int pid,
|
struct bcc_symbol_option *option,
|
struct bcc_symbol *sym) {
|
static struct bcc_symbol_option default_option = {
|
.use_debug_file = 1,
|
.check_debug_file_crc = 1,
|
.use_symbol_type = BCC_SYM_ALL_TYPES,
|
};
|
|
if (module == NULL)
|
return -1;
|
|
memset(sym, 0, sizeof(bcc_symbol));
|
|
if (strchr(module, '/')) {
|
sym->module = strdup(module);
|
} else {
|
sym->module = bcc_procutils_which_so(module, pid);
|
}
|
if (sym->module == NULL)
|
return -1;
|
|
ProcMountNSGuard g(pid);
|
|
sym->name = symname;
|
sym->offset = addr;
|
if (option == NULL)
|
option = &default_option;
|
|
if (sym->name && sym->offset == 0x0)
|
if (bcc_elf_foreach_sym(sym->module, _find_sym, option, sym) < 0)
|
goto invalid_module;
|
if (sym->offset == 0x0)
|
goto invalid_module;
|
|
// For executable (ET_EXEC) binaries, translate the virtual address
|
// to physical address in the binary file.
|
// For shared object binaries (ET_DYN), the address from symbol table should
|
// already be physical address in the binary file.
|
if (bcc_elf_get_type(sym->module) == ET_EXEC) {
|
struct load_addr_t addr = {
|
.target_addr = sym->offset,
|
.binary_addr = 0x0,
|
};
|
if (bcc_elf_foreach_load_section(sym->module, &_find_load, &addr) < 0)
|
goto invalid_module;
|
if (!addr.binary_addr)
|
goto invalid_module;
|
sym->offset = addr.binary_addr;
|
}
|
return 0;
|
|
invalid_module:
|
if (sym->module) {
|
::free(const_cast<char*>(sym->module));
|
sym->module = NULL;
|
}
|
return -1;
|
}
|
}
|