/* * 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 "bcc_syms.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "bcc_elf.h" #include "bcc_perf_map.h" #include "bcc_proc.h" #include "common.h" #include "syms.h" #include "vendor/tinyformat.hpp" ProcSyms::ModulePath::ModulePath(const std::string &ns_path, int root_fd, int pid, bool enter_ns) { if (!enter_ns) { path_ = ns_path; proc_root_path_ = ns_path; return; } proc_root_path_ = tfm::format("/proc/%d/root%s", pid, ns_path); // filename for openat must not contain any starting slashes, otherwise // it would get treated as an absolute path std::string trimmed_path; size_t non_slash_pos; for (non_slash_pos = 0; non_slash_pos < ns_path.size() && ns_path[non_slash_pos] == '/'; non_slash_pos++) ; trimmed_path = ns_path.substr(non_slash_pos); fd_ = openat(root_fd, trimmed_path.c_str(), O_RDONLY); if (fd_ > 0) path_ = tfm::format("/proc/self/fd/%d", fd_); else // openat failed, fall back to /proc/.../root path path_ = proc_root_path_; } bool ProcStat::getinode_(ino_t &inode) { struct stat s; if (!stat(procfs_.c_str(), &s)) { inode = s.st_ino; return true; } else { return false; } } bool ProcStat::refresh_root() { // try to current root and mount namespace for process char current_root[PATH_MAX], current_mount_ns[PATH_MAX]; if (readlink(root_symlink_.c_str(), current_root, PATH_MAX) < 0 || readlink(mount_ns_symlink_.c_str(), current_mount_ns, PATH_MAX) < 0) // readlink failed, process might not exist anymore; keep old fd return false; // check if root fd is up to date if (root_fd_ != -1 && current_root == root_ && current_mount_ns == mount_ns_) return false; root_ = current_root; mount_ns_ = current_mount_ns; // either root fd is invalid or process root and/or mount namespace changed; // re-open root note: when /proc/.../root changes, the open file descriptor // still refers to the old one int original_root_fd = root_fd_; root_fd_ = open(root_symlink_.c_str(), O_PATH); if (root_fd_ == -1) std::cerr << "Opening " << root_symlink_ << " failed: " << strerror(errno) << std::endl; if (original_root_fd > 0) close(original_root_fd); return original_root_fd != root_fd_; } bool ProcStat::is_stale() { ino_t cur_inode; return getinode_(cur_inode) && (cur_inode != inode_) && refresh_root(); } ProcStat::ProcStat(int pid) : procfs_(tfm::format("/proc/%d/exe", pid)), root_symlink_(tfm::format("/proc/%d/root", pid)), mount_ns_symlink_(tfm::format("/proc/%d/ns/mnt", pid)) { getinode_(inode_); refresh_root(); } void KSyms::_add_symbol(const char *symname, const char *modname, uint64_t addr, void *p) { KSyms *ks = static_cast(p); ks->syms_.emplace_back(symname, modname, 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::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 = (*it).mod.c_str(); 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) { if (option) std::memcpy(&symbol_option_, option, sizeof(bcc_symbol_option)); else symbol_option_ = { .use_debug_file = 1, .check_debug_file_crc = 1, .lazy_symbolize = 1, .use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC) }; load_modules(); } void ProcSyms::load_modules() { bcc_procutils_each_module(pid_, _add_module, this); } void ProcSyms::refresh() { modules_.clear(); load_modules(); procstat_.reset(); } int ProcSyms::_add_module(mod_info *mod, int enter_ns, void *payload) { ProcSyms *ps = static_cast(payload); std::shared_ptr modpath = std::make_shared(mod->name, ps->procstat_.get_root_fd(), ps->pid_, enter_ns && ps->pid_ != -1); auto it = std::find_if( ps->modules_.begin(), ps->modules_.end(), [=](const ProcSyms::Module &m) { return m.name_ == mod->name; }); if (it == ps->modules_.end()) { auto module = Module( mod->name, modpath, &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(modpath->path(), &module.elf_so_addr_, &module.elf_so_offset_) < 0) { fprintf(stderr, "WARNING: Couldn't find .text section in %s\n", modpath->alt_path()); fprintf(stderr, "WARNING: BCC can't handle sym look ups for %s", modpath->alt_path()); } } if (!bcc_is_perf_map(modpath->path()) || 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(mod->start_addr, mod->end_addr, mod->file_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, std::shared_ptr path, struct bcc_symbol_option *option) : name_(name), path_(path), loaded_(false), symbol_option_(option), type_(ModuleType::UNKNOWN) { int elf_type = bcc_elf_get_type(path_->path()); // 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(path_->path()) == 1) type_ = ModuleType::PERF_MAP; else if (bcc_elf_is_vdso(path_->path()) == 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(p); auto res = m->symnames_.emplace(symname); m->syms_.emplace_back(&*(res.first), start, size); return 0; } int ProcSyms::Module::_add_symbol_lazy(size_t section_idx, size_t str_table_idx, size_t str_len, uint64_t start, uint64_t size, int debugfile, void *p) { Module *m = static_cast(p); m->syms_.emplace_back( section_idx, str_table_idx, str_len, start, size, debugfile); return 0; } void ProcSyms::Module::load_sym_table() { if (loaded_) return; loaded_ = true; if (type_ == ModuleType::UNKNOWN) return; if (type_ == ModuleType::PERF_MAP) bcc_perf_map_foreach_sym(path_->path(), _add_symbol, this); if (type_ == ModuleType::EXEC || type_ == ModuleType::SO) { if (symbol_option_->lazy_symbolize) bcc_elf_foreach_sym_lazy(path_->path(), _add_symbol_lazy, symbol_option_, this); else bcc_elf_foreach_sym(path_->path(), _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 = __so_calc_mod_offset(range.start, range.file_offset, elf_so_addr_, elf_so_offset_, addr); } else { offset = addr; } return true; } } return false; } bool ProcSyms::Module::find_name(const char *symname, uint64_t *addr) { struct Payload { const char *symname; uint64_t *out; bool found; }; Payload payload; payload.symname = symname; payload.out = addr; payload.found = false; auto cb = [](const char *name, uint64_t start, uint64_t size, void *p) { Payload *payload = static_cast(p); if (!strcmp(payload->symname, name)) { payload->found = true; *(payload->out) = start; return -1; // Stop iteration } return 0; }; if (type_ == ModuleType::PERF_MAP) bcc_perf_map_foreach_sym(path_->path(), cb, &payload); if (type_ == ModuleType::EXEC || type_ == ModuleType::SO) { bcc_elf_foreach_sym(path_->path(), cb, symbol_option_, &payload); if (path_->path() != path_->alt_path()) // some features (e.g. some kinds of debug info) don't work with /proc/self/fd/... path bcc_elf_foreach_sym(path_->alt_path(), cb, symbol_option_, &payload); } if (type_ == ModuleType::VDSO) bcc_elf_foreach_vdso_sym(cb, &payload); if (!payload.found) return false; if (type_ == ModuleType::SO) *(payload.out) += start(); return true; } 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) { // Resolve and cache the symbol name if necessary if (!it->is_name_resolved) { std::string sym_name(it->data.name_idx.str_len + 1, '\0'); if (bcc_elf_symbol_str(path_->path(), it->data.name_idx.section_idx, it->data.name_idx.str_table_idx, &sym_name[0], sym_name.size(), it->data.name_idx.debugfile)) break; it->data.name = &*(symnames_.emplace(std::move(sym_name)).first); it->is_name_resolved = true; } sym->name = it->data.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; } bool BuildSyms::Module::load_sym_table() { if (loaded_) return true; symbol_option_ = { .use_debug_file = 1, .check_debug_file_crc = 1, .lazy_symbolize = 1, .use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC) }; bcc_elf_foreach_sym(module_name_.c_str(), _add_symbol, &symbol_option_, this); std::sort(syms_.begin(), syms_.end()); for(std::vector::iterator it = syms_.begin(); it != syms_.end(); ++it++) { } loaded_ = true; return true; } int BuildSyms::Module::_add_symbol(const char *symname, uint64_t start, uint64_t size, void *p) { BuildSyms::Module *m = static_cast (p); auto res = m->symnames_.emplace(symname); m->syms_.emplace_back(&*(res.first), start, size); return 0; } bool BuildSyms::Module::resolve_addr(uint64_t offset, struct bcc_symbol* sym, bool demangle) { std::vector::iterator it; load_sym_table(); if (syms_.empty()) goto unknown_symbol; it = std::upper_bound(syms_.begin(), syms_.end(), Symbol(nullptr, offset, 0)); if (it != syms_.begin()) { it--; sym->name = (*it).name->c_str(); if (demangle) sym->demangle_name = sym->name; sym->offset = offset - (*it).start; sym->module = module_name_.c_str(); return true; } unknown_symbol: memset(sym, 0, sizeof(struct bcc_symbol)); return false; } bool BuildSyms::add_module(const std::string module_name) { struct stat s; char buildid[BPF_BUILD_ID_SIZE*2+1]; if (stat(module_name.c_str(), &s) < 0) return false; if (bcc_elf_get_buildid(module_name.c_str(), buildid) < 0) return false; std::string elf_buildid(buildid); std::unique_ptr ptr(new BuildSyms::Module(module_name.c_str())); buildmap_[elf_buildid] = std::move(ptr); return true; } bool BuildSyms::resolve_addr(std::string build_id, uint64_t offset, struct bcc_symbol *sym, bool demangle) { std::unordered_map >::iterator it; it = buildmap_.find(build_id); if (it == buildmap_.end()) /*build-id not added to the BuildSym*/ return false; BuildSyms::Module *mod = it->second.get(); return mod->resolve_addr(offset, sym, demangle); } extern "C" { void *bcc_symcache_new(int pid, struct bcc_symbol_option *option) { if (pid < 0) return static_cast(new KSyms()); return static_cast(new ProcSyms(pid, option)); } void bcc_free_symcache(void *symcache, int pid) { if (pid < 0) delete static_cast(symcache); else delete static_cast(symcache); } void bcc_symbol_free_demangle_name(struct bcc_symbol *sym) { if (sym->demangle_name && (sym->demangle_name != sym->name)) free(const_cast(sym->demangle_name)); } int bcc_symcache_resolve(void *resolver, uint64_t addr, struct bcc_symbol *sym) { SymbolCache *cache = static_cast(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(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(resolver); return cache->resolve_name(module, name, addr) ? 0 : -1; } void bcc_symcache_refresh(void *resolver) { SymbolCache *cache = static_cast(resolver); cache->refresh(); } void *bcc_buildsymcache_new(void) { return static_cast(new BuildSyms()); } void bcc_free_buildsymcache(void *symcache) { delete static_cast(symcache); } int bcc_buildsymcache_add_module(void *resolver, const char *module_name) { BuildSyms *bsym = static_cast(resolver); return bsym->add_module(module_name) ? 0 : -1; } int bcc_buildsymcache_resolve(void *resolver, struct bpf_stack_build_id *trace, struct bcc_symbol *sym) { std::string build_id; unsigned char *c = &trace->build_id[0]; int idx = 0; /*cannot resolve in case of fallback*/ if (trace->status == BPF_STACK_BUILD_ID_EMPTY || trace->status == BPF_STACK_BUILD_ID_IP) return 0; while( idx < 20) { int nib1 = (c[idx]&0xf0)>>4; int nib2 = (c[idx]&0x0f); build_id += "0123456789abcdef"[nib1]; build_id += "0123456789abcdef"[nib2]; idx++; } BuildSyms *bsym = static_cast(resolver); return bsym->resolve_addr(build_id, trace->offset, sym) ? 0 : -1; } struct mod_search { const char *name; uint64_t inode; uint64_t dev_major; uint64_t dev_minor; uint64_t addr; uint8_t inode_match_only; uint64_t start; uint64_t file_offset; }; int _bcc_syms_find_module(mod_info *info, int enter_ns, void *p) { struct mod_search *mod = (struct mod_search *)p; // use inode + dev to determine match if inode set if (mod->inode) { if (mod->inode != info->inode) return 0; // look at comment on USDT::set_probe_matching_kludge // in api/BPF.h for explanation of why this might be // necessary if (mod->inode_match_only) goto file_match; if(mod->dev_major == info->dev_major && mod->dev_minor == info->dev_minor) goto file_match; return 0; } // fallback to name match if (!strcmp(info->name, mod->name)) goto file_match; return 0; file_match: mod->start = info->start_addr; mod->file_offset = info->file_offset; return -1; } uint64_t __so_calc_global_addr(uint64_t mod_start_addr, uint64_t mod_file_offset, uint64_t elf_sec_start_addr, uint64_t elf_sec_file_offset, uint64_t offset) { return offset + (mod_start_addr - mod_file_offset) - (elf_sec_start_addr - elf_sec_file_offset); } uint64_t __so_calc_mod_offset(uint64_t mod_start_addr, uint64_t mod_file_offset, uint64_t elf_sec_start_addr, uint64_t elf_sec_file_offset, uint64_t global_addr) { return global_addr - (mod_start_addr - mod_file_offset) + (elf_sec_start_addr - elf_sec_file_offset); } int bcc_resolve_global_addr(int pid, const char *module, const uint64_t address, uint8_t inode_match_only, uint64_t *global) { struct stat s; uint64_t elf_so_addr, elf_so_offset; if (stat(module, &s)) return -1; struct mod_search mod = {module, s.st_ino, major(s.st_dev), minor(s.st_dev), address, inode_match_only, 0x0, 0x0}; if (bcc_procutils_each_module(pid, _bcc_syms_find_module, &mod) < 0 || mod.start == 0x0) return -1; if (bcc_elf_get_text_scn_info(module, &elf_so_addr, &elf_so_offset) < 0) return -1; *global = __so_calc_global_addr(mod.start, mod.file_offset, elf_so_addr, elf_so_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, .lazy_symbolize = 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(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) { int module_type; static struct bcc_symbol_option default_option = { .use_debug_file = 1, .check_debug_file_crc = 1, .lazy_symbolize = 1, #if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2 .use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP), #else .use_symbol_type = BCC_SYM_ALL_TYPES, #endif }; 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; if (pid != 0 && pid != -1 && strstr(sym->module, "/proc") != sym->module){ char *temp = (char*)sym->module; sym->module = strdup(tfm::format("/proc/%d/root%s", pid, sym->module).c_str()); free(temp); } 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 and shared objects (ET_DYN), translate // the virtual address to physical address in the binary file. module_type = bcc_elf_get_type(sym->module); if (module_type == ET_EXEC || module_type == ET_DYN) { 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(sym->module)); sym->module = NULL; } return -1; } }