Commit d516406d authored by 4ast's avatar 4ast

Merge pull request #495 from vmg/vmg/elf-2

[RFC] Remove all dependencies on external binaries
parents b236092d 2153673a
......@@ -11,6 +11,8 @@ include(cmake/GetGitRevisionDescription.cmake)
include(cmake/version.cmake)
include(GNUInstallDirs)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
if(NOT PYTHON_ONLY)
find_package(BISON)
find_package(FLEX)
......@@ -45,6 +47,8 @@ if(NOT DEFINED BCC_KERNEL_MODULES_DIR)
set(BCC_KERNEL_MODULES_DIR "/lib/modules")
endif()
find_package(LibElf REQUIRED)
# Set to non-zero if system installs kernel headers with split source and build
# directories in /lib/modules/`uname -r`/. This is the case for debian and
# suse, to the best of my knowledge.
......
......@@ -14,7 +14,7 @@ Source1: http://llvm.org/releases/%{llvmver}/llvm-%{llvmver}.src.tar.xz
Source2: http://llvm.org/releases/%{llvmver}/cfe-%{llvmver}.src.tar.xz
BuildArch: x86_64
BuildRequires: bison, cmake >= 2.8.7, flex, gcc, gcc-c++, libxml2-devel, python2-devel
BuildRequires: bison, cmake >= 2.8.7, flex, gcc, gcc-c++, libxml2-devel, python2-devel, elfutils-libelf-devel-static
%description
Python bindings for BPF Compiler Collection (BCC). Control a BPF program from
......
......@@ -11,7 +11,7 @@ URL: https://github.com/iovisor/bcc
Source0: bcc.tar.gz
BuildArch: x86_64
BuildRequires: bison, cmake >= 2.8.7, flex, gcc, gcc-c++, python2-devel
BuildRequires: bison, cmake >= 2.8.7, flex, gcc, gcc-c++, python2-devel, elfutils-libelf-devel-static
%description
Python bindings for BPF Compiler Collection (BCC). Control a BPF program from
......
# - Try to find libelf
# Once done this will define
#
# LIBELF_FOUND - system has libelf
# LIBELF_INCLUDE_DIRS - the libelf include directory
# LIBELF_LIBRARIES - Link these to use libelf
# LIBELF_DEFINITIONS - Compiler switches required for using libelf
#
# Copyright (c) 2008 Bernhard Walle <bernhard.walle@gmx.de>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
if (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS)
set (LibElf_FIND_QUIETLY TRUE)
endif (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS)
find_path (LIBELF_INCLUDE_DIRS
NAMES
libelf.h
PATHS
/usr/include
/usr/include/libelf
/usr/local/include
/usr/local/include/libelf
/opt/local/include
/opt/local/include/libelf
/sw/include
/sw/include/libelf
ENV CPATH)
find_library (LIBELF_LIBRARIES
NAMES
elf
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
ENV LIBRARY_PATH
ENV LD_LIBRARY_PATH)
include (FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBELF_FOUND to TRUE if all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibElf DEFAULT_MSG
LIBELF_LIBRARIES
LIBELF_INCLUDE_DIRS)
SET(CMAKE_REQUIRED_LIBRARIES elf)
INCLUDE(CheckCXXSourceCompiles)
CHECK_CXX_SOURCE_COMPILES("#include <libelf.h>
int main() {
Elf *e = (Elf*)0;
size_t sz;
elf_getshdrstrndx(e, &sz);
return 0;
}" ELF_GETSHDRSTRNDX)
mark_as_advanced(LIBELF_INCLUDE_DIRS LIBELF_LIBRARIES ELF_GETSHDRSTRNDX)
......@@ -3,7 +3,7 @@ Maintainer: Brenden Blanco <bblanco@plumgrid.com>
Section: misc
Priority: optional
Standards-Version: 3.9.5
Build-Depends: debhelper (>= 9), cmake, libllvm3.7 | libllvm3.8, llvm-3.7-dev | llvm-3.8-dev, libclang-3.7-dev | libclang-3.8-dev
Build-Depends: debhelper (>= 9), cmake, libllvm3.7 | libllvm3.8, llvm-3.7-dev | llvm-3.8-dev, libclang-3.7-dev | libclang-3.8-dev, libelf-dev
Homepage: https://github.com/iovisor/bcc
Package: libbcc
......
......@@ -139,12 +139,12 @@ return function(BPF, utils)
bpf:attach_kprobe{event="kfree", fn_name="free_enter"}
end
local syms = args.pid and utils.sym.ProcSymbols:new(args.pid) or utils.sym.KSymbols:new()
local syms = BPF.SymbolCache(args.pid)
local allocs = bpf:get_table("allocs")
local stack_traces = bpf:get_table("stack_traces")
local function resolve(addr)
local sym = syms:lookup(addr)
local sym = syms:resolve(addr)
if args.pid == nil then
sym = sym .. " [kernel]"
end
......
......@@ -79,7 +79,7 @@ return function(BPF, utils)
parser:option("-d --duration", "duration to trace for", 9999999):convert(tonumber)
local args = parser:parse()
local ksym = utils.sym.KSymbols:new()
local ksym = BPF.SymbolCache()
local filter = "1"
local MAXDEPTH = 20
......@@ -107,7 +107,7 @@ return function(BPF, utils)
for k, v in counts:items() do
for addr in stack_traces:walk(tonumber(k.stack_id)) do
print(" %-16p %s" % {addr, ksym:lookup(addr)})
print(" %-16p %s" % {addr, ksym:resolve(addr)})
end
print(" %-16s %s" % {"-", ffi.string(k.name)})
print(" %d\n" % tonumber(v))
......
......@@ -8,6 +8,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}/frontends/b)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frontends/b)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frontends/clang)
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${LIBELF_INCLUDE_DIRS})
# todo: if check for kernel version
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/compat)
add_definitions(${LLVM_DEFINITIONS})
......@@ -32,12 +33,12 @@ if (CMAKE_COMPILER_IS_GNUCC)
endif()
endif()
add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc)
add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc bcc_elf.c bcc_proc.c bcc_syms.cc)
set_target_properties(bcc-shared PROPERTIES VERSION ${REVISION_LAST} SOVERSION 0)
set_target_properties(bcc-shared PROPERTIES OUTPUT_NAME bcc)
add_library(bcc-loader-static libbpf.c perf_reader.c)
add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc)
add_library(bcc-loader-static libbpf.c perf_reader.c bcc_elf.c bcc_proc.c)
add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc bcc_syms.cc)
set_target_properties(bcc-static PROPERTIES OUTPUT_NAME bcc)
# BPF is still experimental otherwise it should be available
......@@ -52,8 +53,8 @@ set(clang_libs ${libclangFrontend} ${libclangSerialization} ${libclangDriver} ${
${libclangAST} ${libclangLex} ${libclangBasic})
# Link against LLVM libraries
target_link_libraries(bcc-shared b_frontend clang_frontend ${clang_libs} ${expanded_libs})
target_link_libraries(bcc-static b_frontend clang_frontend bcc-loader-static ${clang_libs} ${expanded_libs})
target_link_libraries(bcc-shared b_frontend clang_frontend ${clang_libs} ${expanded_libs} ${LIBELF_LIBRARIES})
target_link_libraries(bcc-static b_frontend clang_frontend bcc-loader-static ${clang_libs} ${expanded_libs} ${LIBELF_LIBRARIES})
install(TARGETS bcc-shared LIBRARY COMPONENT libbcc
DESTINATION ${CMAKE_INSTALL_LIBDIR})
......
/*
* 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <gelf.h>
#include "bcc_elf.h"
#define NT_STAPSDT 3
static int openelf(const char *path, Elf **elf_out, int *fd_out) {
if (elf_version(EV_CURRENT) == EV_NONE)
return -1;
*fd_out = open(path, O_RDONLY);
if (*fd_out < 0)
return -1;
*elf_out = elf_begin(*fd_out, ELF_C_READ, 0);
if (*elf_out == 0) {
close(*fd_out);
return -1;
}
return 0;
}
static const char *parse_stapsdt_note(struct bcc_elf_usdt *probe,
const char *desc, int elf_class) {
if (elf_class == ELFCLASS32) {
probe->pc = *((uint32_t *)(desc));
probe->base_addr = *((uint32_t *)(desc + 4));
probe->semaphore = *((uint32_t *)(desc + 8));
desc = desc + 12;
} else {
probe->pc = *((uint64_t *)(desc));
probe->base_addr = *((uint64_t *)(desc + 8));
probe->semaphore = *((uint64_t *)(desc + 16));
desc = desc + 24;
}
probe->provider = desc;
desc += strlen(desc) + 1;
probe->name = desc;
desc += strlen(desc) + 1;
probe->arg_fmt = desc;
desc += strlen(desc) + 1;
return desc;
}
static int do_note_segment(Elf_Scn *section, int elf_class,
bcc_elf_probecb callback, const char *binpath,
void *payload) {
Elf_Data *data = NULL;
while ((data = elf_getdata(section, data)) != 0) {
size_t offset = 0;
GElf_Nhdr hdr;
size_t name_off, desc_off;
while ((offset = gelf_getnote(data, offset, &hdr, &name_off, &desc_off)) !=
0) {
const char *desc, *desc_end;
struct bcc_elf_usdt probe;
if (hdr.n_type != NT_STAPSDT)
continue;
if (hdr.n_namesz != 8)
continue;
if (memcmp((const char *)data->d_buf + name_off, "stapsdt", 8) != 0)
continue;
desc = (const char *)data->d_buf + desc_off;
desc_end = desc + hdr.n_descsz;
if (parse_stapsdt_note(&probe, desc, elf_class) == desc_end)
callback(binpath, &probe, payload);
}
}
return 0;
}
static int listprobes(Elf *e, bcc_elf_probecb callback, const char *binpath,
void *payload) {
Elf_Scn *section = NULL;
size_t stridx;
int elf_class = gelf_getclass(e);
if (elf_getshdrstrndx(e, &stridx) != 0)
return -1;
while ((section = elf_nextscn(e, section)) != 0) {
GElf_Shdr header;
char *name;
if (!gelf_getshdr(section, &header))
continue;
if (header.sh_type != SHT_NOTE)
continue;
name = elf_strptr(e, stridx, header.sh_name);
if (name && !strcmp(name, ".note.stapsdt")) {
if (do_note_segment(section, elf_class, callback, binpath, payload) < 0)
return -1;
}
}
return 0;
}
int bcc_elf_foreach_usdt(const char *path, bcc_elf_probecb callback,
void *payload) {
Elf *e;
int fd, res;
if (openelf(path, &e, &fd) < 0)
return -1;
res = listprobes(e, callback, path, payload);
elf_end(e);
close(fd);
return res;
}
static int list_in_scn(Elf *e, Elf_Scn *section, size_t stridx, size_t symsize,
bcc_elf_symcb callback, void *payload) {
Elf_Data *data = NULL;
while ((data = elf_getdata(section, data)) != 0) {
size_t i, symcount = data->d_size / symsize;
if (data->d_size % symsize)
return -1;
for (i = 0; i < symcount; ++i) {
GElf_Sym sym;
const char *name;
if (!gelf_getsym(data, (int)i, &sym))
continue;
if ((name = elf_strptr(e, stridx, sym.st_name)) == NULL)
continue;
if (callback(name, sym.st_value, sym.st_size, sym.st_info, payload) < 0)
break;
}
}
return 0;
}
static int listsymbols(Elf *e, bcc_elf_symcb callback, void *payload) {
Elf_Scn *section = NULL;
while ((section = elf_nextscn(e, section)) != 0) {
GElf_Shdr header;
if (!gelf_getshdr(section, &header))
continue;
if (header.sh_type != SHT_SYMTAB && header.sh_type != SHT_DYNSYM)
continue;
if (list_in_scn(e, section, header.sh_link, header.sh_entsize, callback,
payload) < 0)
return -1;
}
return 0;
}
int bcc_elf_foreach_sym(const char *path, bcc_elf_symcb callback,
void *payload) {
Elf *e;
int fd, res;
if (openelf(path, &e, &fd) < 0)
return -1;
res = listsymbols(e, callback, payload);
elf_end(e);
close(fd);
return res;
}
static int loadaddr(Elf *e, uint64_t *addr) {
size_t phnum, i;
if (elf_getphdrnum(e, &phnum) != 0)
return -1;
for (i = 0; i < phnum; ++i) {
GElf_Phdr header;
if (!gelf_getphdr(e, (int)i, &header))
continue;
if (header.p_type != PT_LOAD)
continue;
*addr = (uint64_t)header.p_vaddr;
return 0;
}
return -1;
}
int bcc_elf_loadaddr(const char *path, uint64_t *address) {
Elf *e;
int fd, res;
if (openelf(path, &e, &fd) < 0)
return -1;
res = loadaddr(e, address);
elf_end(e);
close(fd);
return res;
}
int bcc_elf_is_shared_obj(const char *path) {
Elf *e;
GElf_Ehdr hdr;
int fd, res = -1;
if (openelf(path, &e, &fd) < 0)
return -1;
if (gelf_getehdr(e, &hdr))
res = (hdr.e_type == ET_DYN);
elf_end(e);
close(fd);
return res;
}
#if 0
#include <stdio.h>
int main(int argc, char *argv[])
{
uint64_t addr;
if (bcc_elf_findsym(argv[1], argv[2], -1, STT_FUNC, &addr) < 0)
return -1;
printf("%s: %p\n", argv[2], (void *)addr);
return 0;
}
#endif
/*
* 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.
*/
#ifndef LIBBCC_ELF_H
#define LIBBCC_ELF_H
#ifdef __cplusplus
extern "C" {
#endif
struct bcc_elf_usdt {
uint64_t pc;
uint64_t base_addr;
uint64_t semaphore;
const char *provider;
const char *name;
const char *arg_fmt;
};
typedef void (*bcc_elf_probecb)(const char *, const struct bcc_elf_usdt *,
void *);
typedef int (*bcc_elf_symcb)(const char *, uint64_t, uint64_t, int, void *);
int bcc_elf_foreach_usdt(const char *path, bcc_elf_probecb callback,
void *payload);
int bcc_elf_loadaddr(const char *path, uint64_t *address);
int bcc_elf_foreach_sym(const char *path, bcc_elf_symcb callback,
void *payload);
int bcc_elf_is_shared_obj(const char *path);
#ifdef __cplusplus
}
#endif
#endif
/*
* 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 <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <stdio.h>
#include "bcc_proc.h"
#include "bcc_elf.h"
static bool is_exe(const char *path) {
struct stat s;
if (access(path, X_OK) < 0)
return false;
if (stat(path, &s) < 0)
return false;
return S_ISREG(s.st_mode);
}
char *bcc_procutils_which(const char *binpath) {
char buffer[4096];
const char *PATH;
if (strchr(binpath, '/'))
return is_exe(binpath) ? strdup(binpath) : 0;
if (!(PATH = getenv("PATH")))
return 0;
while (PATH) {
const char *next = strchr(PATH, ':') ?: strchr(PATH, '\0');
const size_t path_len = next - PATH;
if (path_len) {
memcpy(buffer, PATH, path_len);
buffer[path_len] = '/';
strcpy(buffer + path_len + 1, binpath);
if (is_exe(buffer))
return strdup(buffer);
}
PATH = *next ? (next + 1) : 0;
}
return 0;
}
int bcc_procutils_each_module(int pid, bcc_procutils_modulecb callback,
void *payload) {
char procmap_filename[128];
FILE *procmap;
int ret;
sprintf(procmap_filename, "/proc/%ld/maps", (long)pid);
procmap = fopen(procmap_filename, "r");
if (!procmap)
return -1;
do {
char endline[4096];
char perm[8], dev[8];
long long begin, end, size, inode;
ret = fscanf(procmap, "%llx-%llx %s %llx %s %llx", &begin, &end, perm,
&size, dev, &inode);
if (!fgets(endline, sizeof(endline), procmap))
break;
if (ret == 6) {
char *mapname = endline;
char *newline = strchr(endline, '\n');
if (newline)
newline[0] = '\0';
while (isspace(mapname[0])) mapname++;
if (strchr(perm, 'x') && mapname[0] && mapname[0] != '[')
callback(mapname, (uint64_t)begin, (uint64_t)end, payload);
}
} while (ret && ret != EOF);
fclose(procmap);
return 0;
}
int bcc_procutils_each_ksym(bcc_procutils_ksymcb callback, void *payload) {
char line[2048];
FILE *kallsyms;
/* root is needed to list ksym addresses */
if (geteuid() != 0)
return -1;
kallsyms = fopen("/proc/kallsyms", "r");
if (!kallsyms)
return -1;
if (!fgets(line, sizeof(line), kallsyms)) {
fclose(kallsyms);
return -1;
}
while (fgets(line, sizeof(line), kallsyms)) {
char *symname, *endsym;
unsigned long long addr;
addr = strtoull(line, &symname, 16);
endsym = symname = symname + 3;
while (*endsym && !isspace(*endsym)) endsym++;
*endsym = '\0';
callback(symname, addr, payload);
}
fclose(kallsyms);
return 0;
}
#define CACHE1_HEADER "ld.so-1.7.0"
#define CACHE1_HEADER_LEN (sizeof(CACHE1_HEADER) - 1)
#define CACHE2_HEADER "glibc-ld.so.cache"
#define CACHE2_HEADER_LEN (sizeof(CACHE2_HEADER) - 1)
#define CACHE2_VERSION "1.1"
struct ld_cache1_entry {
int32_t flags;
uint32_t key;
uint32_t value;
};
struct ld_cache1 {
char header[CACHE1_HEADER_LEN];
uint32_t entry_count;
struct ld_cache1_entry entries[0];
};
struct ld_cache2_entry {
int32_t flags;
uint32_t key;
uint32_t value;
uint32_t pad1_;
uint64_t pad2_;
};
struct ld_cache2 {
char header[CACHE2_HEADER_LEN];
char version[3];
uint32_t entry_count;
uint32_t string_table_len;
uint32_t pad_[5];
struct ld_cache2_entry entries[0];
};
static int lib_cache_count;
static struct ld_lib {
char *libname;
char *path;
int flags;
} * lib_cache;
static int read_cache1(const char *ld_map) {
struct ld_cache1 *ldcache = (struct ld_cache1 *)ld_map;
const char *ldstrings =
(const char *)(ldcache->entries + ldcache->entry_count);
uint32_t i;
lib_cache =
(struct ld_lib *)malloc(ldcache->entry_count * sizeof(struct ld_lib));
lib_cache_count = (int)ldcache->entry_count;
for (i = 0; i < ldcache->entry_count; ++i) {
const char *key = ldstrings + ldcache->entries[i].key;
const char *val = ldstrings + ldcache->entries[i].value;
const int flags = ldcache->entries[i].flags;
lib_cache[i].libname = strdup(key);
lib_cache[i].path = strdup(val);
lib_cache[i].flags = flags;
}
return 0;
}
static int read_cache2(const char *ld_map) {
struct ld_cache2 *ldcache = (struct ld_cache2 *)ld_map;
uint32_t i;
if (memcmp(ld_map, CACHE2_HEADER, CACHE2_HEADER_LEN))
return -1;
lib_cache =
(struct ld_lib *)malloc(ldcache->entry_count * sizeof(struct ld_lib));
lib_cache_count = (int)ldcache->entry_count;
for (i = 0; i < ldcache->entry_count; ++i) {
const char *key = ld_map + ldcache->entries[i].key;
const char *val = ld_map + ldcache->entries[i].value;
const int flags = ldcache->entries[i].flags;
lib_cache[i].libname = strdup(key);
lib_cache[i].path = strdup(val);
lib_cache[i].flags = flags;
}
return 0;
}
static int load_ld_cache(const char *cache_path) {
struct stat st;
size_t ld_size;
const char *ld_map;
int ret, fd = open(cache_path, O_RDONLY);
if (fd < 0)
return -1;
if (fstat(fd, &st) < 0 || st.st_size < sizeof(struct ld_cache1)) {
close(fd);
return -1;
}
ld_size = st.st_size;
ld_map = (const char *)mmap(NULL, ld_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ld_map == MAP_FAILED) {
close(fd);
return -1;
}
if (memcmp(ld_map, CACHE1_HEADER, CACHE1_HEADER_LEN) == 0) {
const struct ld_cache1 *cache1 = (struct ld_cache1 *)ld_map;
size_t cache1_len = sizeof(struct ld_cache1) +
(cache1->entry_count * sizeof(struct ld_cache1_entry));
cache1_len = (cache1_len + 0x7) & ~0x7ULL;
if (ld_size > (cache1_len + sizeof(struct ld_cache2)))
ret = read_cache2(ld_map + cache1_len);
else
ret = read_cache1(ld_map);
} else {
ret = read_cache2(ld_map);
}
munmap((void *)ld_map, ld_size);
close(fd);
return ret;
}
#define LD_SO_CACHE "/etc/ld.so.cache"
#define FLAG_TYPE_MASK 0x00ff
#define TYPE_ELF_LIBC6 0x0003
#define FLAG_ABI_MASK 0xff00
#define ABI_SPARC_LIB64 0x0100
#define ABI_IA64_LIB64 0x0200
#define ABI_X8664_LIB64 0x0300
#define ABI_S390_LIB64 0x0400
#define ABI_POWERPC_LIB64 0x0500
static bool match_so_flags(int flags) {
if ((flags & FLAG_TYPE_MASK) != TYPE_ELF_LIBC6)
return false;
switch (flags & FLAG_ABI_MASK) {
case ABI_SPARC_LIB64:
case ABI_IA64_LIB64:
case ABI_X8664_LIB64:
case ABI_S390_LIB64:
case ABI_POWERPC_LIB64:
return (sizeof(void *) == 8);
}
return true;
}
const char *bcc_procutils_which_so(const char *libname) {
const size_t soname_len = strlen(libname) + strlen("lib.so");
char soname[soname_len + 1];
int i;
if (strchr(libname, '/'))
return libname;
if (lib_cache_count < 0)
return NULL;
if (!lib_cache_count && load_ld_cache(LD_SO_CACHE) < 0) {
lib_cache_count = -1;
return NULL;
}
snprintf(soname, soname_len + 1, "lib%s.so", libname);
for (i = 0; i < lib_cache_count; ++i) {
if (!strncmp(lib_cache[i].libname, soname, soname_len) &&
match_so_flags(lib_cache[i].flags))
return lib_cache[i].path;
}
return NULL;
}
/*
* 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.
*/
#ifndef LIBBCC_PROC_H
#define LIBBCC_PROC_H
#include "bcc_syms.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*bcc_procutils_modulecb)(const char *, uint64_t, uint64_t,
void *);
typedef void (*bcc_procutils_ksymcb)(const char *, uint64_t, void *);
const char *bcc_procutils_which_so(const char *libname);
char *bcc_procutils_which(const char *binpath);
int bcc_procutils_each_module(int pid, bcc_procutils_modulecb callback,
void *payload);
int bcc_procutils_each_ksym(bcc_procutils_ksymcb callback, void *payload);
#ifdef __cplusplus
}
#endif
#endif
/*
* 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 <vector>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include "bcc_syms.h"
#include "bcc_proc.h"
#include "bcc_elf.h"
class SymbolCache {
public:
virtual void refresh() = 0;
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym) = 0;
virtual bool resolve_name(const char *name, uint64_t *addr) = 0;
};
class KSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t addr) : name(name), addr(addr) {}
std::string name;
uint64_t addr;
bool operator<(const Symbol &rhs) const { return addr < rhs.addr; }
};
std::vector<Symbol> syms_;
std::unordered_map<std::string, uint64_t> symnames_;
static void _add_symbol(const char *, uint64_t, void *);
public:
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *name, uint64_t *addr);
virtual void refresh() {
if (syms_.empty()) {
bcc_procutils_each_ksym(_add_symbol, this);
std::sort(syms_.begin(), syms_.end());
}
}
};
void KSyms::_add_symbol(const char *symname, uint64_t addr, void *p) {
KSyms *ks = static_cast<KSyms *>(p);
ks->syms_.emplace_back(symname, addr);
}
bool KSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
refresh();
if (syms_.empty()) {
sym->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->module = "[kernel]";
sym->offset = addr - (*it).addr;
return true;
}
bool KSyms::resolve_name(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;
}
class ProcStat {
std::string procfs_;
ino_t inode_;
ino_t getinode_() {
struct stat s;
return (!stat(procfs_.c_str(), &s)) ? s.st_ino : -1;
}
public:
ProcStat(int pid) : inode_(-1) {
char buffer[128];
snprintf(buffer, sizeof(buffer), "/proc/%d/exe", pid);
procfs_ = buffer;
}
bool is_stale() { return inode_ != getinode_(); }
void reset() { inode_ = getinode_(); }
};
class ProcSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t start, uint64_t size, int flags = 0)
: name(name), start(start), size(size), flags(flags) {}
std::string name;
uint64_t start;
uint64_t size;
int flags;
};
struct Module {
Module(const char *name, uint64_t start, uint64_t end)
: name_(name), start_(start), end_(end) {}
std::string name_;
uint64_t start_;
uint64_t end_;
std::vector<Symbol> syms_;
void load_sym_table();
bool decode_sym(uint64_t addr, struct bcc_symbol *sym);
bool is_so() { return strstr(name_.c_str(), ".so") != nullptr; }
static int _add_symbol(const char *symname, uint64_t start, uint64_t end,
int flags, void *p);
};
int pid_;
std::vector<Module> modules_;
ProcStat procstat_;
static void _add_module(const char *, uint64_t, uint64_t, void *);
public:
ProcSyms(int pid);
virtual void refresh();
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *name, uint64_t *addr);
};
ProcSyms::ProcSyms(int pid) : pid_(pid), procstat_(pid) { refresh(); }
void ProcSyms::refresh() {
modules_.clear();
bcc_procutils_each_module(pid_, _add_module, this);
procstat_.reset();
}
void 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);
}
bool ProcSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
if (procstat_.is_stale())
refresh();
sym->module = nullptr;
sym->name = nullptr;
sym->offset = 0x0;
for (Module &mod : modules_) {
if (addr >= mod.start_ && addr <= mod.end_)
return mod.decode_sym(addr, sym);
}
return false;
}
bool ProcSyms::resolve_name(const char *name, uint64_t *addr) {
*addr = 0x0;
return false;
}
int ProcSyms::Module::_add_symbol(const char *symname, uint64_t start,
uint64_t end, int flags, void *p) {
Module *m = static_cast<Module *>(p);
m->syms_.emplace_back(symname, start, end, flags);
return 0;
}
void ProcSyms::Module::load_sym_table() {
if (syms_.size())
return;
bcc_elf_foreach_sym(name_.c_str(), _add_symbol, this);
}
bool ProcSyms::Module::decode_sym(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;
for (Symbol &s : syms_) {
if (offset >= s.start && offset <= (s.start + s.size)) {
sym->name = s.name.c_str();
sym->offset = (offset - s.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));
}
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 *name,
uint64_t *addr) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
return cache->resolve_name(name, addr) ? 0 : -1;
}
void bcc_symcache_refresh(void *resolver) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
cache->refresh();
}
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;
if (!strcmp(sym->name, symname)) {
sym->offset = addr;
return -1;
}
return 0;
}
int bcc_resolve_symname(const char *module, const char *symname,
const uint64_t addr, 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 = module;
} else {
sym->module = bcc_procutils_which_so(module);
}
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)
bcc_elf_foreach_sym(sym->module, _find_sym, sym);
if (sym->offset == 0x0)
return -1;
sym->offset = (sym->offset - load_addr);
return 0;
}
}
/*
* 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.
*/
#ifndef LIBBCC_SYMS_H
#define LIBBCC_SYMS_H
#ifdef __cplusplus
extern "C" {
#endif
struct bcc_symbol {
const char *name;
const char *module;
uint64_t offset;
};
void *bcc_symcache_new(int pid);
int bcc_symcache_resolve(void *symcache, uint64_t addr, struct bcc_symbol *sym);
int bcc_symcache_resolve_name(void *resolver, const char *name, uint64_t *addr);
void bcc_symcache_refresh(void *resolver);
int bcc_resolve_symname(const char *module, const char *symname,
const uint64_t addr, struct bcc_symbol *sym);
#ifdef __cplusplus
}
#endif
#endif
include(cmake/FindLuaJIT.cmake)
find_package(LuaJIT)
if (LUAJIT_LIBRARIES)
find_program(LUAJIT luajit)
......
......@@ -18,13 +18,12 @@ local libbcc = require("bcc.libbcc")
local TracerPipe = require("bcc.tracerpipe")
local Table = require("bcc.table")
local LD = require("bcc.ld")
local Sym = require("bcc.sym")
local Bpf = class("BPF")
Bpf.static.open_kprobes = {}
Bpf.static.open_uprobes = {}
Bpf.static.process_symbols = {}
Bpf.static.KPROBE_LIMIT = 1000
Bpf.static.tracer_pipe = nil
Bpf.static.DEFAULT_CFLAGS = {
......@@ -63,6 +62,10 @@ function Bpf.static.cleanup_probes()
end
end
function Bpf.static.SymbolCache(pid)
return Sym.create_cache(pid)
end
function Bpf.static.num_open_uprobes()
return table.count(Bpf.static.open_uprobes)
end
......@@ -71,19 +74,6 @@ function Bpf.static.num_open_kprobes()
return table.count(Bpf.static.open_kprobes)
end
function Bpf.static.usymaddr(pid, addr, refresh)
local proc_sym = Bpf.static.process_symbols[pid]
if proc_sym == nil then
proc_sym = ProcSymbols(pid)
Bpf.static.process_symbols[pid] = proc_sym
elseif refresh then
proc_sym.refresh()
end
return proc_sym.decode_addr(addr)
end
Bpf.static.SCRIPT_ROOT = "./"
function Bpf.static.script_root(root)
local dir, file = root:match'(.*/)(.*)'
......@@ -185,7 +175,7 @@ end
function Bpf:attach_uprobe(args)
Bpf.check_probe_quota(1)
local path, addr = LD.check_path_symbol(args.name, args.sym, args.addr)
local path, addr = Sym.check_path_symbol(args.name, args.sym, args.addr)
local fn = self:load_func(args.fn_name, 'BPF_PROG_TYPE_KPROBE')
local ptype = args.retprobe and "r" or "p"
local ev_name = string.format("%s_%s_0x%p", ptype, path:gsub("[^%a%d]", "_"), addr)
......
......@@ -14,7 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
]]
require("bcc.vendor.helpers")
return {
BPF = require("bcc.bpf"),
sym = require("bcc.sym"),
}
return { BPF = require("bcc.bpf") }
--[[
Copyright 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.
]]
local ffi = require("ffi")
local posix = require("bcc.vendor.posix")
local _find_library_cache = {}
local function _find_library(name)
if _find_library_cache[name] ~= nil then
return _find_library_cache[name]
end
local arch = ffi.arch
local abi_type = "libc6"
if ffi.abi("64bit") then
if arch == "x64" then
abi_type = abi_type .. ",x86-64"
elseif arch == "ppc" or arch == "mips" then
abi_type = abi_type .. ",64bit"
end
end
local pattern = "%s+lib" .. name:escape() .. "%.%S+ %(" .. abi_type:escape() .. ".-%) => (%S+)"
local f = assert(io.popen("/sbin/ldconfig -p"))
local path = nil
for line in f:lines() do
path = line:match(pattern)
if path then break end
end
f:close()
if path then
_find_library_cache[name] = path
end
return path
end
local _find_load_address_cache = {}
local function _find_load_address(path)
if _find_load_address_cache[path] ~= nil then
return _find_load_address_cache[path]
end
local addr = os.spawn(
[[/usr/bin/objdump -x %s | awk '$1 == "LOAD" && $3 ~ /^[0x]*$/ { print $5 }']],
path)
if addr then
addr = posix.tonumber64(addr, 16)
_find_load_address_cache[path] = addr
end
return addr
end
local _find_symbol_cache = {}
local function _find_symbol(path, sym)
assert(path and sym)
if _find_symbol_cache[path] == nil then
_find_symbol_cache[path] = {}
end
local symbols = _find_symbol_cache[path]
if symbols[sym] ~= nil then
return symbols[sym]
end
local addr = os.spawn(
[[/usr/bin/objdump -tT %s | awk -v sym=%s '$NF == sym && $4 == ".text" { print $1; exit }']],
path, sym)
if addr then
addr = posix.tonumber64(addr, 16)
symbols[sym] = addr
end
return addr
end
local function _check_path_symbol(name, sym, addr)
assert(name)
local path = name:sub(1,1) == "/" and name or _find_library(name)
assert(path, "could not find library "..name)
-- TODO: realpath
local load_addr = _find_load_address(path)
assert(load_addr, "could not find load address for "..path)
if addr == nil and sym ~= nil then
addr = _find_symbol(path, sym)
end
assert(addr, "could not find address of symbol "..sym)
return path, (addr - load_addr)
end
return {
check_path_symbol=_check_path_symbol,
find_symbol=_find_symbol,
find_load_address=_find_load_address,
find_library=_find_library
}
......@@ -98,4 +98,18 @@ int perf_reader_fd(struct perf_reader *reader);
void perf_reader_set_fd(struct perf_reader *reader, int fd);
]]
ffi.cdef[[
struct bcc_symbol {
const char *name;
const char *module;
uint64_t offset;
};
int bcc_resolve_symname(const char *module, const char *symname, const uint64_t addr,
struct bcc_symbol *sym);
void *bcc_symcache_new(int pid);
int bcc_symcache_resolve(void *symcache, uint64_t addr, struct bcc_symbol *sym);
void bcc_symcache_refresh(void *resolver);
]]
return ffi.load(os.getenv("LIBBCC_SO_PATH") or rawget(_G, "LIBBCC_SO_PATH") or "bcc")
......@@ -59,7 +59,6 @@ return function()
local utils = {
argparse = require("bcc.vendor.argparse"),
posix = require("bcc.vendor.posix"),
sym = require("bcc.sym"),
}
local command = dofile(tracefile)
......
......@@ -13,166 +13,34 @@ 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.
]]
local posix = require("bcc.vendor.posix")
local ProcSymbols = class("ProcSymbols")
function ProcSymbols:initialize(pid)
self.pid = pid
self:refresh()
end
function ProcSymbols:_get_exe()
return os.spawn("readlink -f /proc/%d/exe", self.pid)
end
function ProcSymbols:_get_start_time()
return tonumber(os.spawn("cut -d' ' -f 22 /proc/%d/stat", self.pid))
end
function ProcSymbols:_get_code_ranges()
local function is_binary_segment(parts)
if #parts ~= 6 then return false end
if parts[6]:starts("[") then return false end
if parts[2]:find("x") == nil then return false end
return true
end
local ranges = {}
local cmd = string.format("/proc/%d/maps", self.pid)
for line in io.lines(cmd) do
local parts = line:split()
if is_binary_segment(parts) then
local binary = parts[6]
local range = parts[1]:split("-", true)
assert(#range == 2)
ranges[binary] = {posix.tonumber64(range[1], 16), posix.tonumber64(range[2], 16)}
end
end
return ranges
end
function ProcSymbols:refresh()
self.code_ranges = self:_get_code_ranges()
self.ranges_cache = {}
self.exe = self:_get_exe()
self.start_time = self:_get_start_time()
end
function ProcSymbols:_check_pid_wrap()
local new_exe = self:_get_exe()
local new_time = self:_get_start_time()
if self.exe ~= new_exe or self.start_time ~= new_time then
self:refresh()
end
end
function ProcSymbols:_get_sym_ranges(binary)
if self.ranges_cache[binary] ~= nil then
return self.ranges_cache[binary]
end
local function is_function_sym(parts)
return #parts == 6 and parts[4] == ".text" and parts[3] == "F"
end
local sym_ranges = {}
local proc = assert(io.popen("objdump -t "..binary))
for line in proc:lines() do
local parts = line:split()
if is_function_sym(parts) then
local sym_start = posix.tonumber64(parts[1], 16)
local sym_len = posix.tonumber64(parts[5], 16)
local sym_name = parts[6]
sym_ranges[sym_name] = {sym_start, sym_len}
end
end
proc:close()
self.ranges_cache[binary] = sym_ranges
return sym_ranges
end
function ProcSymbols:_decode_sym(binary, offset)
local sym_ranges = self:_get_sym_ranges(binary)
for name, range in pairs(sym_ranges) do
local start = range[1]
local length = range[2]
if offset >= start and offset <= (start + length) then
return string.format("%s+0x%p", name, offset - start)
local ffi = require("ffi")
local libbcc = require("bcc.libbcc")
local SYM = ffi.typeof("struct bcc_symbol[1]")
local function create_cache(pid)
return {
_CACHE = libbcc.bcc_symcache_new(pid or -1),
resolve = function(self, addr)
local sym = SYM()
if libbcc.bcc_symcache_resolve(self._CACHE, addr, sym) < 0 then
return "[unknown]", 0x0
end
return ffi.string(sym[0].name), sym[0].offset
end
end
return string.format("%p", offset)
}
end
function ProcSymbols:lookup(addr)
self:_check_pid_wrap()
for binary, range in pairs(self.code_ranges) do
local start = range[1]
local tend = range[2]
if addr >= start and addr <= tend then
local offset = binary:ends(".so") and (addr - start) or addr
return string.format("%s [%s]", self:_decode_sym(binary, offset), binary)
local function check_path_symbol(module, symname, addr)
local sym = SYM()
if libbcc.bcc_resolve_symname(module, symname, addr or 0x0, sym) < 0 then
if sym[0].module == nil then
error("could not find library '%s' in the library path" % module)
else
error("failed to resolve symbol '%s' in '%s'" % {
symname, ffi.string(sym[0].module)})
end
end
return string.format("%p", addr)
end
local KSymbols = class("KSymbols")
KSymbols.static.KALLSYMS = "/proc/kallsyms"
function KSymbols:initialize()
self.ksyms = {}
self.ksym_names = {}
self.loaded = false
end
function KSymbols:_load()
if self.loaded then return end
local first_line = true
for line in io.lines(KSymbols.KALLSYMS) do
if not first_line then
local cols = line:split()
local name = cols[3]
local addr = posix.tonumber64(cols[1], 16)
table.insert(self.ksyms, {name, addr})
self.ksym_names[name] = #self.ksyms
end
first_line = false
end
self.loaded = true
end
function KSymbols:_addr2index(addr)
self:_load()
return table.bsearch(self.ksyms, addr, function(v) return v[2] end)
end
function KSymbols:lookup(addr, with_offset)
local idx = self:_addr2index(addr)
if idx == nil then
return "[unknown]"
end
if with_offset then
local offset = addr - self.ksyms[idx][2]
return "%s %p" % {self.ksyms[idx][1], offset}
else
return self.ksyms[idx][1]
end
end
function KSymbols:refresh()
-- NOOP
return ffi.string(sym[0].module), sym[0].offset
end
return { ProcSymbols=ProcSymbols, KSymbols=KSymbols }
return { create_cache=create_cache, check_path_symbol=check_path_symbol }
......@@ -11,7 +11,6 @@ Module "bcc.sym" "bcc/sym.lua"
Module "bcc.libbcc" "bcc/libbcc.lua"
Module "bcc.tracerpipe" "bcc/tracerpipe.lua"
Module "bcc.table" "bcc/table.lua"
Module "bcc.ld" "bcc/ld.lua"
Main "bcc/run.lua"
Output "bcc.lua"
......@@ -25,7 +25,7 @@ import struct
import sys
basestring = (unicode if sys.version_info[0] < 3 else str)
from .libbcc import lib, _CB_TYPE
from .libbcc import lib, _CB_TYPE, bcc_symbol
from .procstat import ProcStat, ProcUtils
from .table import Table
from .tracepoint import Perf, Tracepoint
......@@ -35,10 +35,6 @@ open_kprobes = {}
open_uprobes = {}
tracefile = None
TRACEFS = "/sys/kernel/debug/tracing"
KALLSYMS = "/proc/kallsyms"
ksyms = []
ksym_names = {}
ksym_loaded = 0
_kprobe_limit = 1000
DEBUG_LLVM_IR = 0x1
......@@ -67,6 +63,22 @@ def _check_probe_quota(num_new_probes):
if len(open_kprobes) + len(open_uprobes) + num_new_probes > _kprobe_limit:
raise Exception("Number of open probes would exceed quota")
class KernelSymbolCache(object):
def __init__(self):
self.cache = lib.bcc_symcache_new(-1)
def resolve(self, addr):
sym = bcc_symbol()
psym = ct.pointer(sym)
if lib.bcc_symcache_resolve(self.cache, addr, psym) < 0:
return "[unknown]", 0
return sym.name, sym.offset
def resolve_name(self, name):
addr = ct.c_ulonglong()
if lib.bcc_symcache_resolve_name(self.cache, name, ct.pointer(addr)) < 0:
return -1
return addr.value
class BPF(object):
SOCKET_FILTER = 1
......@@ -75,9 +87,7 @@ class BPF(object):
SCHED_ACT = 4
_probe_repl = re.compile("[^a-zA-Z0-9_]")
_libsearch_cache = {}
_lib_load_address_cache = {}
_lib_symbol_cache = {}
_ksym_cache = KernelSymbolCache()
_auto_includes = {
"linux/time.h" : ["time"],
......@@ -413,84 +423,14 @@ class BPF(object):
del open_kprobes[ev_name]
@classmethod
def find_library(cls, name):
if name in cls._libsearch_cache:
return cls._libsearch_cache[name]
if struct.calcsize("l") == 4:
machine = os.uname()[4] + "-32"
else:
machine = os.uname()[4] + "-64"
mach_map = {
"x86_64-64": "libc6,x86-64",
"ppc64-64": "libc6,64bit",
"sparc64-64": "libc6,64bit",
"s390x-64": "libc6,64bit",
"ia64-64": "libc6,IA-64",
}
abi_type = mach_map.get(machine, "libc6")
expr = r"\s+lib%s\.[^\s]+\s+\(%s[^)]*[^/]+([^\s]+)" % (name, abi_type)
with os.popen("/sbin/ldconfig -p 2>/dev/null") as f:
data = f.read()
res = re.search(expr, data)
if not res:
return None
path = res.group(1)
cls._libsearch_cache[name] = path
return path
@classmethod
def find_load_address(cls, path):
if path in cls._lib_load_address_cache:
return cls._lib_load_address_cache[path]
# "LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x..."
with os.popen("""/usr/bin/objdump -x %s | \
awk '$1 == "LOAD" && $3 ~ /^[0x]*$/ \
{ print $5 }'""" % path) as f:
data = f.read().rstrip()
if not data:
return None
addr = int(data, 16)
cls._lib_load_address_cache[path] = addr
cls._lib_symbol_cache[path] = {}
return addr
@classmethod
def find_symbol(cls, path, sym):
# initialized in find_load_address
symbols = cls._lib_symbol_cache[path]
if sym in symbols:
return symbols[sym]
with os.popen("""/usr/bin/objdump -tT %s | \
awk -v sym=%s '$NF == sym && ($4 == ".text" \
|| $4 == "text.hot" || $4 == "text.unlikely") \
{ print $1; exit }'""" % (path, sym)) as f:
data = f.read().rstrip()
if not data:
return None
addr = int(data, 16)
symbols[sym] = addr
return addr
@classmethod
def _check_path_symbol(cls, name, sym, addr):
if name.startswith("/"):
path = name
else:
path = BPF.find_library(name)
if not path:
raise Exception("could not find library %s" % name)
path = os.path.realpath(path)
load_addr = BPF.find_load_address(path)
if not addr and sym:
addr = BPF.find_symbol(path, sym)
if not addr:
raise Exception("could not determine address of symbol %s" % sym)
return (path, addr-load_addr)
def _check_path_symbol(cls, module, symname, addr):
sym = bcc_symbol()
psym = ct.pointer(sym)
if lib.bcc_resolve_symname(module, symname, addr or 0x0, psym) < 0:
if not sym.module:
raise Exception("could not find library %s" % module)
raise Exception("could not determine address of symbol %s" % symname)
return sym.module, sym.offset
def attach_uprobe(self, name="", sym="", addr=None,
fn_name="", pid=-1, cpu=0, group_fd=-1):
......@@ -678,52 +618,15 @@ class BPF(object):
except KeyboardInterrupt:
exit()
@staticmethod
def _load_kallsyms():
global ksym_loaded, ksyms, ksym_names
if ksym_loaded:
return
try:
syms = open(KALLSYMS, "r")
except:
raise Exception("Could not read %s" % KALLSYMS)
line = syms.readline()
for line in iter(syms):
cols = line.split()
name = cols[2]
addr = int(cols[0], 16)
# keep a mapping of names to ksyms index
ksym_names[name] = len(ksyms)
ksyms.append((name, addr))
syms.close()
ksym_loaded = 1
@staticmethod
def _ksym_addr2index(addr):
global ksyms
start = -1
end = len(ksyms)
while end != start + 1:
mid = int((start + end) / 2)
if addr < ksyms[mid][1]:
end = mid
else:
start = mid
return start
@staticmethod
def ksym(addr):
"""ksym(addr)
Translate a kernel memory address into a kernel function name, which is
returned. This is a simple translator that uses /proc/kallsyms.
returned.
"""
global ksyms
BPF._load_kallsyms()
idx = BPF._ksym_addr2index(addr)
if idx == -1:
return "[unknown]"
return ksyms[idx][0]
name, _ = BPF._ksym_cache.resolve(addr)
return name
@staticmethod
def ksymaddr(addr):
......@@ -731,15 +634,10 @@ class BPF(object):
Translate a kernel memory address into a kernel function name plus the
instruction offset as a hexidecimal number, which is returned as a
string. This is a simple translator that uses /proc/kallsyms.
string.
"""
global ksyms
BPF._load_kallsyms()
idx = BPF._ksym_addr2index(addr)
if idx == -1:
return "[unknown]"
offset = int(addr - ksyms[idx][1])
return "%s+0x%x" % (ksyms[idx][0], offset)
name, offset = BPF._ksym_cache.resolve(addr)
return "%s+0x%x" % (name, offset)
@staticmethod
def ksymname(name):
......@@ -747,35 +645,7 @@ class BPF(object):
Translate a kernel name into an address. This is the reverse of
ksymaddr. Returns -1 when the function name is unknown."""
global ksyms, ksym_names
BPF._load_kallsyms()
idx = ksym_names.get(name, -1)
if idx == -1:
return 0
return ksyms[idx][1]
@classmethod
def usymaddr(cls, pid, addr, refresh_symbols=False):
"""usymaddr(pid, addr, refresh_symbols=False)
Decode the specified address in the specified process to a symbolic
representation that includes the symbol name, offset within the symbol,
and the module name. See the ProcessSymbols class for more details.
Specify refresh_symbols=True if you suspect the set of loaded modules
or their load addresses has changed since the last time you called
usymaddr() on this pid.
"""
proc_sym = None
if pid in cls._process_symbols:
proc_sym = cls._process_symbols[pid]
if refresh_symbols:
proc_sym.refresh_code_ranges()
else:
proc_sym = ProcessSymbols(pid)
cls._process_symbols[pid] = proc_sym
return proc_sym.decode_addr(addr)
return BPF._ksym_cache.resolve_name(name)
@staticmethod
def num_open_kprobes():
......
......@@ -102,3 +102,28 @@ lib.perf_reader_free.restype = None
lib.perf_reader_free.argtypes = [ct.c_void_p]
lib.perf_reader_fd.restype = int
lib.perf_reader_fd.argtypes = [ct.c_void_p]
# bcc symbol helpers
class bcc_symbol(ct.Structure):
_fields_ = [
('name', ct.c_char_p),
('module', ct.c_char_p),
('offset', ct.c_ulonglong),
]
lib.bcc_resolve_symname.restype = ct.c_int
lib.bcc_resolve_symname.argtypes = [
ct.c_char_p, ct.c_char_p, ct.c_ulonglong, ct.POINTER(bcc_symbol)]
lib.bcc_symcache_new.restype = ct.c_void_p
lib.bcc_symcache_new.argtypes = [ct.c_int]
lib.bcc_symcache_resolve.restype = ct.c_int
lib.bcc_symcache_resolve.argtypes = [ct.c_void_p, ct.c_ulonglong, ct.POINTER(bcc_symbol)]
lib.bcc_symcache_resolve_name.restype = ct.c_int
lib.bcc_symcache_resolve_name.argtypes = [
ct.c_void_p, ct.c_char_p, ct.POINTER(ct.c_ulonglong)]
lib.bcc_symcache_refresh.restype = None
lib.bcc_symcache_refresh.argtypes = [ct.c_void_p]
......@@ -11,8 +11,8 @@
# 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.
from subprocess import Popen, PIPE, STDOUT
import ctypes as ct
from .libbcc import lib, bcc_symbol
class ProcessSymbols(object):
def __init__(self, pid):
......@@ -21,79 +21,10 @@ class ProcessSymbols(object):
Call refresh_code_ranges() periodically if you anticipate changes
in the set of loaded libraries or their addresses.
"""
self.pid = pid
self.refresh_code_ranges()
self.cache = lib.bcc_symcache_new(pid)
def refresh_code_ranges(self):
self.code_ranges = self._get_code_ranges()
self.ranges_cache = {}
self.procstat = ProcStat(self.pid)
@staticmethod
def _is_binary_segment(parts):
return len(parts) == 6 and parts[5][0] != '[' and 'x' in parts[1]
def _get_code_ranges(self):
ranges = {}
raw_ranges = open("/proc/%d/maps" % self.pid).readlines()
# A typical line from /proc/PID/maps looks like this:
# 7f21b6635000-7f21b67eb000 r-xp ... /usr/lib64/libc-2.21.so
# We are looking for executable segments that have a .so file
# or the main executable. The first two lines are the range of
# that memory segment, which we index by binary name.
for raw_range in raw_ranges:
parts = raw_range.split()
if not ProcessSymbols._is_binary_segment(parts):
continue
binary = parts[5]
range_parts = parts[0].split('-')
addr_range = (int(range_parts[0], 16), int(range_parts[1], 16))
ranges[binary] = addr_range
return ranges
@staticmethod
def _is_function_symbol(parts):
return len(parts) == 6 and parts[3] == ".text" and parts[2] == "F"
@staticmethod
def _run_command_get_output(command):
p = Popen(command.split(), stdout=PIPE, stderr=STDOUT)
return iter(p.stdout.readline, b'')
def _get_sym_ranges(self, binary):
if binary in self.ranges_cache:
return self.ranges_cache[binary]
sym_ranges = {}
raw_symbols = ProcessSymbols._run_command_get_output(
"objdump -t %s" % binary)
for raw_symbol in raw_symbols:
# A typical line from objdump -t looks like this:
# 00000000004007f5 g F .text 000000000000010e main
# We only care about functions in the .text segment.
# The first number is the start address, and the second
# number is the length.
parts = raw_symbol.split()
if not ProcessSymbols._is_function_symbol(parts):
continue
sym_start = int(parts[0], 16)
sym_len = int(parts[4], 16)
sym_name = parts[5]
sym_ranges[sym_name] = (sym_start, sym_len)
self.ranges_cache[binary] = sym_ranges
return sym_ranges
def _decode_sym(self, binary, offset):
sym_ranges = self._get_sym_ranges(binary)
# Find the symbol that contains the specified offset.
# There might not be one.
for name, (start, length) in sym_ranges.items():
if offset >= start and offset <= (start + length):
return "%s+0x%x" % (name, offset - start)
return "%x" % offset
def _check_pid_wrap(self):
if self.procstat.is_stale():
self.refresh_code_ranges()
lib.bcc_symcache_refresh(self.cache)
def decode_addr(self, addr):
"""
......@@ -103,16 +34,10 @@ class ProcessSymbols(object):
the hex string and the module. If we do have a symbol for it,
return the symbol and the module, e.g. "readline+0x10 [bash]".
"""
self._check_pid_wrap()
# Find the binary that contains the specified address.
# For .so files, look at the relative address; for the main
# executable, look at the absolute address.
for binary, (start, end) in self.code_ranges.items():
if addr >= start and addr <= end:
offset = addr - start \
if binary.endswith(".so") else addr
return "%s [%s]" % (self._decode_sym(binary, offset),
binary)
return "%x" % addr
from . import ProcStat
sym = bcc_symbol()
psym = ct.pointer(sym)
if lib.bcc_symcache_resolve(self.cache, addr, psym) < 0:
if sym.module and sym.offset:
return "0x%x [%s]" % (sym.offset, sym.module)
return "%x" % addr
return "%s+0x%x [%s]" % (sym.name, sym.offset, sym.module)
......@@ -7,3 +7,8 @@ add_executable(test_static test_static.c)
target_link_libraries(test_static bcc-static)
add_test(NAME c_test_static COMMAND ${TEST_WRAPPER} c_test_static sudo ${CMAKE_CURRENT_BINARY_DIR}/test_static)
add_executable(test_c_api test_c_api.c)
target_link_libraries(test_c_api bcc-shared dl)
add_test(NAME test_c_api COMMAND ${TEST_WRAPPER} c_test_api sudo ${CMAKE_CURRENT_BINARY_DIR}/test_c_api)
This diff is collapsed.
#include <stdint.h>
#include <unistd.h>
#include <dlfcn.h>
#include "sput.h"
#include "bcc_elf.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
static void test_procutils__which_so(void) {
const char *libm = bcc_procutils_which_so("m");
sput_fail_unless(libm, "find libm");
sput_fail_unless(libm[0] == '/', "resolve libm absolute path");
sput_fail_unless(strstr(libm, "libm.so"), "resolve libm so");
}
static void test_procutils__which(void) {
char *ld = bcc_procutils_which("ld");
sput_fail_unless(ld, "find `ld` binary");
sput_fail_unless(ld[0] == '/', "find `ld` absolute path");
free(ld);
}
static void _test_ksym(const char *sym, uint64_t addr, void *_) {
if (!strcmp(sym, "startup_64")) {
sput_fail_unless(addr == 0xffffffff81000000ull, "ksym `startup_64`");
} else if (!strcmp(sym, "__per_cpu_start"))
sput_fail_unless(addr == 0x0, "ksym `__per_cpu_start`");
}
static void test_procutils__each_ksym(void) {
sput_fail_unless(geteuid() == 0, "ensure we are root");
bcc_procutils_each_ksym(_test_ksym, NULL);
}
static void test_syms__resolve_symname(void) {
struct bcc_symbol sym;
sput_fail_unless(bcc_resolve_symname("c", "malloc", 0x0, &sym) == 0,
"bcc_resolve_symname(c, malloc)");
sput_fail_unless(strstr(sym.module, "libc.so"), "resolve to module");
sput_fail_unless(sym.module[0] == '/', "resolve to abspath");
sput_fail_unless(sym.offset != 0, "resolve sym offset");
}
static void test_syms__resolver_pid(void) {
struct bcc_symbol sym;
void *resolver = bcc_symcache_new(getpid());
sput_fail_unless(resolver, "create a new resolver for PID");
sput_fail_unless(bcc_symcache_resolve(
resolver, (uint64_t)&test_syms__resolver_pid, &sym) == 0,
"resolve the current function address");
char *this_exe = realpath("/proc/self/exe", NULL);
sput_fail_unless(strcmp(this_exe, sym.module) == 0,
"resolve a function to our own binary");
free(this_exe);
sput_fail_unless(strcmp("test_syms__resolver_pid", sym.name) == 0,
"resolve a function to its actual name");
void *libbcc = dlopen("libbcc.so", RTLD_LAZY | RTLD_NOLOAD);
sput_fail_unless(libbcc, "dlopen(libbcc.so)");
void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
sput_fail_unless(libbcc_fptr, "dlsym(bcc_resolve_symname)");
sput_fail_unless(
bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0,
"resolve a function in libbcc in our current process");
sput_fail_unless(strstr(sym.module, "libbcc.so"),
"resolve a function to the loaded libbcc module");
sput_fail_unless(strcmp("bcc_resolve_symname", sym.name) == 0,
"resolve a function in libbcc to its actual name");
void *libc_fptr = dlsym(NULL, "strtok");
sput_fail_unless(libc_fptr, "dlsym(strtok)");
sput_fail_unless(
bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0,
"resolve a function in libc in our current process");
sput_fail_unless(
sym.module && sym.module[0] == '/' && strstr(sym.module, "libc"),
"resolve a function to linked libc module");
sput_fail_unless(strcmp("strtok", sym.name) == 0,
"resolve a function in libc to its actual name");
}
int main(int argc, char *argv[]) {
sput_start_testing();
sput_enter_suite("procutils: which_so");
sput_run_test(test_procutils__which_so);
sput_enter_suite("procutils: which");
sput_run_test(test_procutils__which);
sput_enter_suite("procutils: each_ksym");
sput_run_test(test_procutils__each_ksym);
sput_enter_suite("syms: resolve_symname");
sput_run_test(test_syms__resolve_symname);
sput_enter_suite("syms: resolver_pid");
sput_run_test(test_syms__resolver_pid);
sput_finish_testing();
return sput_get_return_value();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment