diff --git a/SPECS/bcc+clang.spec b/SPECS/bcc+clang.spec index bbb6dd4c9538..be7595f8e576 100644 --- a/SPECS/bcc+clang.spec +++ b/SPECS/bcc+clang.spec @@ -79,6 +79,12 @@ Requires: python-bcc %description -n bcc-tools Command line tools for BPF Compiler Collection (BCC) +%package -n bpfd +Summary: Proxy daemon for remote target devices +Requires: libbcc = %{version}-%{release} +%description -n bpfd +Proxy daemon for remote target devices + %files -n python-bcc %{python_sitelib}/bcc* @@ -99,3 +105,6 @@ Command line tools for BPF Compiler Collection (BCC) /usr/share/bcc/introspection/* /usr/share/bcc/tools/* /usr/share/bcc/man/* + +%files -n bpfd +/usr/bin/bpfd diff --git a/SPECS/bcc.spec b/SPECS/bcc.spec index 01550414b317..30faadb60fd4 100644 --- a/SPECS/bcc.spec +++ b/SPECS/bcc.spec @@ -91,6 +91,12 @@ Requires: python-bcc = %{version}-%{release} %description -n bcc-tools Command line tools for BPF Compiler Collection (BCC) +%package -n bpfd +Summary: Proxy daemon for remote target devices +Requires: libbcc = %{version}-%{release} +%description -n bpfd +Proxy daemon for remote target devices + %files -n libbcc /usr/lib64/* /usr/include/bcc/* @@ -117,6 +123,9 @@ Command line tools for BPF Compiler Collection (BCC) /usr/share/bcc/tools/* /usr/share/bcc/man/* +%files -n bpfd +/usr/bin/bpfd + %post -n libbcc -p /sbin/ldconfig %postun -n libbcc -p /sbin/ldconfig diff --git a/debian/bpfd.install b/debian/bpfd.install new file mode 100644 index 000000000000..d98ddb5ba58c --- /dev/null +++ b/debian/bpfd.install @@ -0,0 +1 @@ +usr/bin/bpfd diff --git a/debian/control b/debian/control index db11fba00473..393239e5ef77 100644 --- a/debian/control +++ b/debian/control @@ -43,3 +43,8 @@ Package: bcc-lua Architecture: all Depends: libbcc Description: Standalone tool to run BCC tracers written in Lua + +Package: bpfd +Architecture: all +Depends: libbcc +Description: Proxy daemon for remote target devices diff --git a/debian/libbcc.install b/debian/libbcc.install index 0cb867e236c0..7829b318552b 100644 --- a/debian/libbcc.install +++ b/debian/libbcc.install @@ -1,3 +1,4 @@ usr/include/bcc/* usr/lib/*/libbcc* +usr/lib/*/libbpf* usr/lib/*/pkgconfig/libbcc.pc diff --git a/src/cc/CMakeLists.txt b/src/cc/CMakeLists.txt index cfeba6196b8a..efcfa624051b 100644 --- a/src/cc/CMakeLists.txt +++ b/src/cc/CMakeLists.txt @@ -81,6 +81,7 @@ if(ENABLE_USDT) endif() add_subdirectory(frontends) +add_subdirectory(bpfd) # Link against LLVM libraries target_link_libraries(bcc-shared ${bcc_common_libs_for_s}) diff --git a/src/cc/bpf_common.cc b/src/cc/bpf_common.cc index 4a71197892a9..b02df57c96ca 100644 --- a/src/cc/bpf_common.cc +++ b/src/cc/bpf_common.cc @@ -17,6 +17,14 @@ #include "bpf_module.h" extern "C" { + +// This stores the appropriate callback function to invoke when there is a +// need to create a map. This is done so that the Python interface can pass +// custom callbacks to create maps for situations wherein the map creation +// functions provided by libbbc are not sufficient (e.g. when there is a need +// to create maps on remote target devices). +bpf_create_map_cb_t bpf_create_map_cb; + void * bpf_module_create_b(const char *filename, const char *proto_filename, unsigned flags) { auto mod = new ebpf::BPFModule(flags); if (mod->load_b(filename, proto_filename) != 0) { @@ -26,7 +34,11 @@ void * bpf_module_create_b(const char *filename, const char *proto_filename, uns return mod; } -void * bpf_module_create_c(const char *filename, unsigned flags, const char *cflags[], int ncflags) { +void *bpf_module_create_c(const char *filename, unsigned flags, + const char *cflags[], int ncflags, + bpf_create_map_cb_t map_cb) { + bpf_create_map_cb = map_cb; + auto mod = new ebpf::BPFModule(flags); if (mod->load_c(filename, cflags, ncflags) != 0) { delete mod; @@ -35,7 +47,11 @@ void * bpf_module_create_c(const char *filename, unsigned flags, const char *cfl return mod; } -void * bpf_module_create_c_from_string(const char *text, unsigned flags, const char *cflags[], int ncflags) { +void *bpf_module_create_c_from_string(const char *text, unsigned flags, + const char *cflags[], int ncflags, + bpf_create_map_cb_t map_cb) { + bpf_create_map_cb = map_cb; + auto mod = new ebpf::BPFModule(flags); if (mod->load_string(text, cflags, ncflags) != 0) { delete mod; diff --git a/src/cc/bpf_common.h b/src/cc/bpf_common.h index 0abdbd4870c8..309206c1a3f6 100644 --- a/src/cc/bpf_common.h +++ b/src/cc/bpf_common.h @@ -24,9 +24,14 @@ extern "C" { #endif +typedef void (*bpf_create_map_cb_t)(void *data); void * bpf_module_create_b(const char *filename, const char *proto_filename, unsigned flags); -void * bpf_module_create_c(const char *filename, unsigned flags, const char *cflags[], int ncflags); -void * bpf_module_create_c_from_string(const char *text, unsigned flags, const char *cflags[], int ncflags); +void *bpf_module_create_c(const char *filename, unsigned flags, + const char *cflags[], int ncflags, + bpf_create_map_cb_t map_cb); +void *bpf_module_create_c_from_string(const char *text, unsigned flags, + const char *cflags[], int ncflags, + bpf_create_map_cb_t map_cb); void bpf_module_destroy(void *program); char * bpf_module_license(void *program); unsigned bpf_module_kern_version(void *program); diff --git a/src/cc/bpfd/CMakeLists.txt b/src/cc/bpfd/CMakeLists.txt new file mode 100644 index 000000000000..57155b4fc08e --- /dev/null +++ b/src/cc/bpfd/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) Jazel Canseco, 2018 +# Licensed under the Apache License, Version 2.0 (the "License") + +add_executable(bpfd bpfd.c base64.c remote_perf_reader.c utils.c cmd_parsers.c) +target_link_libraries(bpfd bpf-shared) +target_link_libraries(bpfd bcc-shared) + +install(TARGETS bpfd RUNTIME DESTINATION bin) diff --git a/src/cc/bpfd/base64.c b/src/cc/bpfd/base64.c new file mode 100644 index 000000000000..89e77a0ddea0 --- /dev/null +++ b/src/cc/bpfd/base64.c @@ -0,0 +1,267 @@ +/* + * Base64 Encoding/Decoding Library + * + * Author: freecode http://freecode-freecode.blogspot.com/ + * + * 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 +#include +#include +#include +#include +#include +#include + +/** +* characters used for Base64 encoding +*/ +const char *BASE64_CHARS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** +* encode three bytes using base64 (RFC 3548) +* +* @param triple three bytes that should be encoded +* @param result buffer of four characters where the result is stored +*/ +void _base64_encode_triple(unsigned char triple[3], char result[4]) { + int tripleValue, i; + + tripleValue = triple[0]; + tripleValue *= 256; + tripleValue += triple[1]; + tripleValue *= 256; + tripleValue += triple[2]; + + for (i = 0; i < 4; i++) { + result[3 - i] = BASE64_CHARS[tripleValue % 64]; + tripleValue /= 64; + } +} + +/** +* encode an array of bytes using Base64 (RFC 3548) +* +* @param source the source buffer +* @param sourcelen the length of the source buffer +* @param target the target buffer +* @param targetlen the length of the target buffer +* @return 1 on success, 0 otherwise +*/ +int base64_encode(unsigned char *source, size_t sourcelen, char *target, + size_t targetlen) { + /* check if the result will fit in the target buffer */ + if ((sourcelen + 2) / 3 * 4 > targetlen - 1) + return 0; + + /* encode all full triples */ + while (sourcelen >= 3) { + _base64_encode_triple(source, target); + sourcelen -= 3; + source += 3; + target += 4; + } + + /* encode the last one or two characters */ + if (sourcelen > 0) { + unsigned char temp[3]; + memset(temp, 0, sizeof(temp)); + memcpy(temp, source, sourcelen); + _base64_encode_triple(temp, target); + target[3] = '='; + if (sourcelen == 1) + target[2] = '='; + + target += 4; + } + + /* terminate the string */ + target[0] = 0; + + return 1; +} + +/** +* determine the value of a base64 encoding character +* +* @param base64char the character of which the value is searched +* @return the value in case of success (0-63), -1 on failure +*/ +int _base64_char_value(char base64char) { + if (base64char >= 'A' && base64char <= 'Z') + return base64char - 'A'; + if (base64char >= 'a' && base64char <= 'z') + return base64char - 'a' + 26; + if (base64char >= '0' && base64char <= '9') + return base64char - '0' + 2 * 26; + if (base64char == '+') + return 2 * 26 + 10; + if (base64char == '/') + return 2 * 26 + 11; + return -1; +} + +/** +* decode a 4 char base64 encoded byte triple +* +* @param quadruple the 4 characters that should be decoded +* @param result the decoded data +* @return lenth of the result (1, 2 or 3), 0 on failure +*/ +int _base64_decode_triple(char quadruple[4], unsigned char *result) { + int i, triple_value, bytes_to_decode = 3, only_equals_yet = 1; + int char_value[4]; + + for (i = 0; i < 4; i++) + char_value[i] = _base64_char_value(quadruple[i]); + + /* check if the characters are valid */ + for (i = 3; i >= 0; i--) { + if (char_value[i] < 0) { + if (only_equals_yet && quadruple[i] == '=') { + /* we will ignore this character anyway, make it something + * that does not break our calculations */ + char_value[i] = 0; + bytes_to_decode--; + continue; + } + return 0; + } + /* after we got a real character, no other '=' are allowed anymore */ + only_equals_yet = 0; + } + + /* if we got "====" as input, bytes_to_decode is -1 */ + if (bytes_to_decode < 0) + bytes_to_decode = 0; + + /* make one big value out of the partial values */ + triple_value = char_value[0]; + triple_value *= 64; + triple_value += char_value[1]; + triple_value *= 64; + triple_value += char_value[2]; + triple_value *= 64; + triple_value += char_value[3]; + + /* break the big value into bytes */ + for (i = bytes_to_decode; i < 3; i++) + triple_value /= 256; + for (i = bytes_to_decode - 1; i >= 0; i--) { + result[i] = triple_value % 256; + triple_value /= 256; + } + + return bytes_to_decode; +} + +/** +* decode base64 encoded data +* +* @param source the encoded data (zero terminated) +* @param target pointer to the target buffer +* @param targetlen length of the target buffer +* @return length of converted data on success, -1 otherwise +*/ +size_t base64_decode(char *source, unsigned char *target, size_t targetlen) { + char *src, *tmpptr; + char quadruple[4], tmpresult[3]; + int i; + int tmplen = 3; + size_t converted = 0; + + /* concatinate '===' to the source to handle unpadded base64 data */ + src = (char *)malloc(strlen(source) + 5); + if (src == NULL) + return -1; + strcpy(src, source); + strcat(src, "===="); + tmpptr = src; + + /* convert as long as we get a full result */ + while (tmplen == 3) { + /* get 4 characters to convert */ + for (i = 0; i < 4; i++) { + /* skip invalid characters - we won't reach the end */ + while (*tmpptr != '=' && _base64_char_value(*tmpptr) < 0) + tmpptr++; + + quadruple[i] = *(tmpptr++); + } + + /* convert the characters */ + tmplen = _base64_decode_triple(quadruple, (unsigned char *)tmpresult); + + /* check if the fit in the result buffer */ + if (targetlen < tmplen) { + free(src); + return -1; + } + + /* put the partial result in the result buffer */ + memcpy(target, tmpresult, tmplen); + target += tmplen; + targetlen -= tmplen; + converted += tmplen; + } + + free(src); + return converted; +} + +void test_base64(char *file) { + struct stat st; + char *fileout, *encoded, *filebuf; + size_t size; + int ret; + FILE *fp; + char *target; + + stat(file, &st); + size = st.st_size; + + fileout = (char *)malloc(strlen(file) + 1 + 4); + fileout[0] = 0; + strcat(fileout, file); + strcat(fileout, ".b64dec"); + + printf("Encoding and then decoding %s into %s, filesize is %d\n", file, + fileout, (int)size); + + encoded = (char *)malloc((size * 4) + 1); + encoded[(size * 4)] = 0; + + filebuf = (char *)malloc(size); + fp = fopen(file, "rb"); + fread(filebuf, size, 1, fp); + + ret = base64_encode((unsigned char *)filebuf, size, encoded, size * 4); + + printf("encoded len: %d\n", (int)strlen(encoded)); + + printf("encoded stat: %s\n", encoded); + + target = (char *)malloc(size); + + ret = base64_decode(encoded, (unsigned char*)target, size); + + fp = fopen(fileout, "wb"); + + printf("fp=%p ret=%d fileout=%s\n", (void *)fp, ret, fileout); + + fwrite(target, size, 1, fp); + + fclose(fp); +} diff --git a/src/cc/bpfd/base64.h b/src/cc/bpfd/base64.h new file mode 100644 index 000000000000..0a6c903060e4 --- /dev/null +++ b/src/cc/bpfd/base64.h @@ -0,0 +1,4 @@ +int base64_encode(unsigned char *source, size_t sourcelen, char *target, + size_t targetlen); +size_t base64_decode(char *source, unsigned char *target, size_t targetlen); +void test_base64(char *file); diff --git a/src/cc/bpfd/bpfd.c b/src/cc/bpfd/bpfd.c new file mode 100644 index 000000000000..ab8671a1cb92 --- /dev/null +++ b/src/cc/bpfd/bpfd.c @@ -0,0 +1,801 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * + * Copyright (C) 2017 Joel Fernandes + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bcc_syms.h" +#include "bpfd.h" + +#define LINEBUF_SIZE 2000000 + +#define DEFAULT_MAX_PID 32768 + +struct usym_cache { + int pid; + char *exe_path; + void *cache; +}; + +int bpf_prog_load_handle(int type, char *name, char *bin_b64, int prog_len, + char *license, unsigned int kern_version) { + int bin_len, ret; + char *bin_buf = NULL; + const struct bpf_insn *insns = NULL; + + bin_len = strlen(bin_b64); + bin_buf = (char *)malloc(bin_len); + + if (!base64_decode(bin_b64, (unsigned char *)bin_buf, bin_len)) + return -1; + + insns = (const struct bpf_insn *)bin_buf; + + /* TODO: logging disabled for now, add mechanism in future */ + ret = bpf_prog_load((enum bpf_prog_type)type, name, insns, prog_len, + (const char *)license, kern_version, 0, NULL, 0); + + printf("bpf_prog_load: ret=%d\n", ret); + return ret; +} + +int get_trace_events(char *tracefs, char *category) { + int res = 0; + + int buf_len = strlen(tracefs) + strlen("/events/") + strlen(category) + 1; + char *tracef = (char *)malloc(buf_len); + snprintf(tracef, buf_len, "%s/events/%s", tracefs, category); + + res = cat_dir(tracef, 1); + + free(tracef); + return res; +} + +int get_trace_events_categories(char *tracefs) { + int res = 0; + + int buf_len = strlen(tracefs) + strlen("/events") + 1; + char *tracef = (char *)malloc(buf_len); + snprintf(tracef, buf_len, "%s/events", tracefs); + + res = cat_dir(tracef, 1); + + free(tracef); + return res; +} + +int bpf_remote_update_elem(int map_fd, char *kstr, int klen, char *lstr, + int llen, unsigned long flags) { + int ret = -ENOMEM; + void *kbin = NULL, *lbin = NULL; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_update; + + lbin = (void *)malloc(llen); + if (!lbin) + goto err_update; + + ret = -EINVAL; + if (!base64_decode(kstr, kbin, klen)) + goto err_update; + + if (!base64_decode(lstr, lbin, llen)) + goto err_update; + + ret = bpf_update_elem(map_fd, kbin, lbin, flags); + +err_update: + if (kbin) + free(kbin); + if (lbin) + free(lbin); + return ret; +} + +char *bpf_remote_lookup_elem(int map_fd, char *kstr, int klen, int llen) { + void *lbin = NULL, *kbin = NULL; + char *lstr = NULL, *rets = NULL; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_update; + + lbin = (void *)malloc(llen); + if (!lbin) + goto err_update; + + lstr = (char *)malloc(llen * 4); + + if (!lstr || !base64_decode(kstr, kbin, klen) || + (bpf_lookup_elem(map_fd, kbin, lbin) < 0)) + goto err_update; + + if (base64_encode(lbin, llen, lstr, llen * 4)) + rets = (char *)lstr; + +err_update: + if (lbin) + free(lbin); + if (kbin) + free(kbin); + if (!rets && lstr) + free(lstr); + return rets; +} + +char *bpf_remote_get_first_key_dump_all(int map_fd, int klen, int llen) { + void *kbin = NULL, *lbin = NULL, *next_kbin = NULL, *tmp = NULL; + int ret, dump_buf_len = 4096, dump_used = 1; + char *dump_buf = NULL, *kstr = NULL, *lstr = NULL, *rets = NULL; + +/* length of base64 buffer with newlines considered */ +#define KSTR_SIZE ((klen * 2) + 2) +#define LSTR_SIZE ((llen * 2) + 2) + + dump_buf = (char *)malloc(dump_buf_len); + kbin = (void *)malloc(klen); + lbin = (void *)malloc(llen); + kstr = (char *)malloc(KSTR_SIZE); + lstr = (char *)malloc(LSTR_SIZE); + + if (!dump_buf || !kbin || !lbin || !lstr || !kstr) + goto err_get; + + if (bpf_get_first_key(map_fd, kbin, klen) < 0) + goto get_done; + + dump_buf[0] = 0; + + do { + next_kbin = (void *)malloc(klen); + if (!next_kbin) + goto err_get; + + if (bpf_lookup_elem(map_fd, kbin, lbin) < 0) + goto err_get; + + if (!base64_encode(kbin, klen, kstr, KSTR_SIZE) || + !base64_encode(lbin, llen, lstr, LSTR_SIZE)) + goto err_get; + + if (dump_buf_len - dump_used < (LSTR_SIZE + KSTR_SIZE)) { + dump_buf_len *= 2; + dump_buf = (char *)realloc(dump_buf, dump_buf_len); + } + + strcat(kstr, "\n"); + strcat(lstr, "\n"); + strncat(dump_buf, kstr, dump_buf_len); + strncat(dump_buf, lstr, dump_buf_len); + dump_used += (KSTR_SIZE + LSTR_SIZE); + + ret = bpf_get_next_key(map_fd, kbin, next_kbin); + + tmp = kbin; + kbin = next_kbin; + next_kbin = NULL; + free(tmp); + } while (ret >= 0); + + rets = dump_buf; + goto get_done; + +err_get: + printf("bpf_remote_get_first_key_dump_all: error condition\n"); + if (dump_buf) + free(dump_buf); + +get_done: + if (kbin) + free(kbin); + if (lbin) + free(lbin); + if (kstr) + free(kstr); + if (lstr) + free(lstr); + if (next_kbin) + free(next_kbin); + return rets; +} + +char *bpf_remote_get_first_key(int map_fd, int klen) { + void *kbin = NULL; + char *kstr = NULL, *rets = NULL; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_get; + + kstr = (char *)malloc(klen * 4); + if (!kstr || bpf_get_first_key(map_fd, kbin, klen) < 0) + goto err_get; + + if (base64_encode(kbin, klen, kstr, klen * 4)) + rets = kstr; + +err_get: + if (kbin) + free(kbin); + if (!rets && kstr) + free(kstr); + return rets; +} + +char *bpf_remote_get_next_key(int map_fd, char *kstr, int klen) { + void *kbin = NULL, *next_kbin = NULL; + char *next_kstr = NULL, *rets = NULL; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_update; + + next_kbin = (void *)malloc(klen); + if (!next_kbin) + goto err_update; + + next_kstr = (char *)malloc(klen * 4); + + if (!next_kstr || !base64_decode(kstr, kbin, klen) || + (bpf_get_next_key(map_fd, kbin, next_kbin) < 0)) + goto err_update; + + if (base64_encode(next_kbin, klen, next_kstr, klen * 4)) + rets = (char *)next_kstr; + +err_update: + if (kbin) + free(kbin); + if (next_kbin) + free(next_kbin); + if (!rets && next_kstr) + free(next_kstr); + return rets; +} + +int bpf_remote_delete_elem(int map_fd, char *kstr, int klen) { + void *kbin = NULL; + int ret = -ENOMEM; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_update; + + ret = -1; + if (!base64_decode(kstr, kbin, klen)) + goto err_update; + + ret = bpf_delete_elem(map_fd, kbin); + +err_update: + if (kbin) + free(kbin); + return ret; +} + +/* + * Clear a map by iterating over keys. + * Return delete error code if any deletes or allocs fail + * else return how many keys were iterated and deleted. + */ +int bpf_clear_map(int map_fd, int klen) { + void *kbin = NULL, *next_kbin = NULL, *tmp = NULL; + int count = 0, ret = -ENOMEM; + + kbin = (void *)malloc(klen); + if (!kbin) + goto err_clear; + + if (bpf_get_first_key(map_fd, kbin, klen) < 0) { + ret = 0; + goto err_clear; + } + + do { + next_kbin = (void *)malloc(klen); + if (!next_kbin) { + ret = -ENOMEM; + goto err_clear; + } + + ret = bpf_delete_elem(map_fd, kbin); + if (ret < 0) + goto err_clear; + count++; + + ret = bpf_get_next_key(map_fd, kbin, next_kbin); + + tmp = kbin; + kbin = next_kbin; + next_kbin = NULL; + free(tmp); + } while (ret >= 0); + + ret = count; + +err_clear: + if (kbin) + free(kbin); + if (next_kbin) + free(next_kbin); + return ret; +} + +char *get_pid_exe(int pid) { + const int PATHBUF_SIZE = 4096; + + char *exe_path = (char *)malloc(PATHBUF_SIZE); + char exe_link[PATHBUF_SIZE]; + int num_chars_read = 0; + + snprintf(exe_link, PATHBUF_SIZE, "/proc/%d/exe", pid); + num_chars_read = readlink(exe_link, exe_path, PATHBUF_SIZE); + if (num_chars_read < 0) + num_chars_read = 0; + exe_path[num_chars_read] = '\0'; + return exe_path; +} + +struct usym_cache *get_or_set_usym_cache(int pid, + struct usym_cache *usym_caches[]) { + struct usym_cache *usym_cache = usym_caches[pid % DEFAULT_MAX_PID]; + + char *exe_path = get_pid_exe(pid); + if (!usym_cache || usym_cache->pid != pid || + strcmp(usym_cache->exe_path, exe_path)) { + if (!usym_cache) { + usym_cache = (struct usym_cache *)malloc(sizeof(struct usym_cache)); + } else { + free(usym_cache->exe_path); + bcc_free_symcache(usym_cache->cache, usym_cache->pid); + } + + usym_cache->pid = pid; + usym_cache->exe_path = exe_path; + usym_cache->cache = bcc_symcache_new(pid, NULL); + + usym_caches[pid % DEFAULT_MAX_PID] = usym_cache; + } else { + free(exe_path); + } + + return usym_cache; +} + +void free_usym_caches(struct usym_cache **usym_caches) { + int i; + for (i = 0; i < DEFAULT_MAX_PID; i++) { + if (usym_caches[i]) { + free(usym_caches[i]->exe_path); + bcc_free_symcache(usym_caches[i]->cache, usym_caches[i]->pid); + + free(usym_caches[i]); + } + } + + free(usym_caches); +} + +int main(int argc, char **argv) { + struct user_input *in = NULL; + char line_buf[LINEBUF_SIZE]; + int arg_index = 0; + void *ksym_cache = NULL; + struct usym_cache **usym_caches = (struct usym_cache **)calloc( + DEFAULT_MAX_PID, sizeof(struct usym_cache *)); + + printf("STARTED_BPFD\n"); + + while (fgets(line_buf, LINEBUF_SIZE, stdin)) { + line_buf[strcspn(line_buf, "\n")] = '\0'; + + /* Empty input */ + if (!strlen(line_buf)) + continue; + + in = parse_user_input(line_buf); + arg_index = 0; + + if (!strcmp(in->cmd, "exit")) { + free_user_input(in); + in = NULL; + break; + } + + printf("START_BPFD_OUTPUT\n"); + fflush(stdout); + + if (!strcmp(in->cmd, "GET_KALLSYMS")) { + if (cat_file("/proc/kallsyms") < 0) + goto invalid_command; + + } else if (!strcmp(in->cmd, "GET_KPROBES_BLACKLIST")) { + char *tracefs; + + PARSE_STR(tracefs); + + if (cat_tracefs_file(tracefs, "../kprobes/blacklist") < 0) + goto invalid_command; + + } else if (!strcmp(in->cmd, "GET_TRACE_EVENTS_CATEGORIES")) { + char *tracefs; + + PARSE_STR(tracefs); + + if (get_trace_events_categories(tracefs) < 0) + goto invalid_command; + + } else if (!strcmp(in->cmd, "GET_TRACE_EVENTS")) { + char *tracefs, *category; + + PARSE_STR(tracefs); + PARSE_STR(category); + + if (get_trace_events(tracefs, category) < 0) + goto invalid_command; + + } else if (!strcmp(in->cmd, "COMM_FOR_PID")) { + int pid; + + PARSE_INT(pid); + + if (cat_comm_file(pid) < 0) + goto invalid_command; + + } else if (!strcmp(in->cmd, "BPF_PROG_LOAD")) { + int prog_len, type; + char *license, *bin_data, *name; + unsigned int kern_version; + + PARSE_INT(type); + PARSE_STR(name); + PARSE_INT(prog_len); + PARSE_STR(license); + PARSE_UINT(kern_version); + PARSE_STR(bin_data); + + if (!strcmp(name, "__none__")) + name = NULL; + bpf_prog_load_handle(type, name, bin_data, prog_len, license, + kern_version); + + } else if (!strcmp(in->cmd, "BPF_ATTACH_KPROBE")) { + int ret, prog_fd, type; + char *ev_name, *fn_name; + + PARSE_INT(prog_fd); + PARSE_INT(type); + PARSE_STR(ev_name); + PARSE_STR(fn_name); + + ret = bpf_attach_kprobe(prog_fd, type, ev_name, fn_name); + printf("bpf_attach_kprobe: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_DETACH_KPROBE")) { + int ret; + char *evname; + + PARSE_STR(evname); + ret = bpf_detach_kprobe(evname); + printf("bpf_detach_kprobe: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_ATTACH_UPROBE")) { + int ret, prog_fd, type, pid; + char *ev_name, *binary_path; + uint64_t offset; + + PARSE_INT(prog_fd); + PARSE_INT(type); + PARSE_STR(ev_name); + PARSE_STR(binary_path); + PARSE_UINT64(offset); + PARSE_INT(pid); + + ret = bpf_attach_uprobe(prog_fd, type, ev_name, binary_path, offset, pid); + printf("bpf_attach_uprobe: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_DETACH_UPROBE")) { + int ret; + char *evname; + + PARSE_STR(evname); + ret = bpf_detach_uprobe(evname); + printf("bpf_detach_uprobe: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_ATTACH_TRACEPOINT")) { + int ret, prog_fd; + char *tpname, *category; + + PARSE_INT(prog_fd); + PARSE_STR(category); + PARSE_STR(tpname); + + ret = bpf_attach_tracepoint(prog_fd, category, tpname); + printf("bpf_attach_tracepoint: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_DETACH_TRACEPOINT")) { + int ret; + char *tpname, *category; + + PARSE_STR(category); + PARSE_STR(tpname); + + ret = bpf_detach_tracepoint(category, tpname); + printf("bpf_detach_tracepoint: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_ATTACH_PERF_EVENT")) { + int ret, progfd, pid, cpu, group_fd; + uint32_t ev_type, ev_config; + uint64_t sample_period, sample_freq; + + PARSE_INT(progfd); + PARSE_UINT32(ev_type); + PARSE_UINT32(ev_config); + PARSE_UINT64(sample_period); + PARSE_UINT64(sample_freq); + PARSE_INT(pid); + PARSE_INT(cpu); + PARSE_INT(group_fd); + + ret = bpf_attach_perf_event(progfd, ev_type, ev_config, sample_period, + sample_freq, pid, cpu, group_fd); + printf("bpf_attach_perf_event: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_CLOSE_PERF_EVENT_FD")) { + int fd, ret; + + PARSE_INT(fd); + ret = bpf_close_perf_event_fd(fd); + printf("bpf_close_perf_event_fd: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_CREATE_MAP")) { + int ret, type, key_size, value_size, max_entries, map_flags; + char *name; + + PARSE_INT(type); + PARSE_STR(name); + PARSE_INT(key_size); + PARSE_INT(value_size); + PARSE_INT(max_entries); + PARSE_INT(map_flags); + + if (!strcmp(name, "__none__")) + name = NULL; + ret = bpf_create_map((enum bpf_map_type)type, name, key_size, value_size, + max_entries, map_flags); + printf("bpf_create_map: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_OPEN_PERF_BUFFER")) { + int pid, cpu, page_cnt, ret; + + PARSE_INT(pid); + PARSE_INT(cpu); + PARSE_INT(page_cnt); + + ret = bpf_remote_open_perf_buffer(pid, cpu, page_cnt); + printf("bpf_open_perf_buffer: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_UPDATE_ELEM")) { + int map_fd, klen, llen, ret; + unsigned long long flags; + char *kstr, *lstr; + + PARSE_INT(map_fd); + PARSE_STR(kstr); + PARSE_INT(klen); + PARSE_STR(lstr); + PARSE_INT(llen); + PARSE_ULL(flags); + + ret = bpf_remote_update_elem(map_fd, kstr, klen, lstr, llen, flags); + printf("bpf_update_elem: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_LOOKUP_ELEM")) { + int map_fd, klen, llen; + char *kstr, *lstr; + + PARSE_INT(map_fd); + PARSE_STR(kstr); + PARSE_INT(klen); + PARSE_INT(llen); + + lstr = bpf_remote_lookup_elem(map_fd, kstr, klen, llen); + if (!lstr) + printf("bpf_lookup_elem: ret=%d\n", -1); + else + printf("%s\n", lstr); + if (lstr) + free(lstr); + + } else if (!strcmp(in->cmd, "BPF_GET_FIRST_KEY")) { + int map_fd, klen, llen, dump_all; + char *kstr; + + PARSE_INT(map_fd); + PARSE_INT(klen); + PARSE_INT(llen); + PARSE_INT(dump_all); + + if (dump_all) + kstr = bpf_remote_get_first_key_dump_all(map_fd, klen, llen); + else + kstr = bpf_remote_get_first_key(map_fd, klen); + + if (!kstr) + printf("bpf_get_first_key: ret=%d\n", -1); + else + printf("%s\n", kstr); + if (kstr) + free(kstr); + + } else if (!strcmp(in->cmd, "BPF_GET_NEXT_KEY")) { + int map_fd, klen; + char *kstr, *next_kstr; + + PARSE_INT(map_fd); + PARSE_STR(kstr); + PARSE_INT(klen); + + next_kstr = bpf_remote_get_next_key(map_fd, kstr, klen); + if (!next_kstr) + printf("bpf_get_next_key: ret=%d\n", -1); + else + printf("%s\n", next_kstr); + if (next_kstr) + free(next_kstr); + + } else if (!strcmp(in->cmd, "BPF_DELETE_ELEM")) { + int map_fd, klen, ret; + char *kstr; + + PARSE_INT(map_fd); + PARSE_STR(kstr); + PARSE_INT(klen); + + ret = bpf_remote_delete_elem(map_fd, kstr, klen); + printf("bpf_delete_elem: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "BPF_CLEAR_MAP")) { + int map_fd, klen, ret; + + PARSE_INT(map_fd); + PARSE_INT(klen); + + ret = bpf_clear_map(map_fd, klen); + printf("bpf_clear_map: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "PERF_READER_POLL")) { + int len, *fds, i, timeout, ret; + + PARSE_INT(timeout); + PARSE_INT(len); + + fds = (void *)malloc(len); + if (!fds) + printf("perf_reader_poll: ret=%d\n", -ENOMEM); + + for (i = 0; i < len; i++) { + PARSE_INT(fds[i]); + } + + ret = remote_perf_reader_poll(fds, len, timeout); + if (ret < 0) + printf("perf_reader_poll: ret=%d\n", ret); + + } else if (!strcmp(in->cmd, "GET_KSYM_NAME")) { + int ret; + uint64_t addr; + struct bcc_symbol sym; + + PARSE_UINT64(addr); + + if (!ksym_cache) + ksym_cache = bcc_symcache_new(-1, NULL); + + ret = bcc_symcache_resolve_no_demangle(ksym_cache, addr, &sym); + printf("GET_KSYM_NAME: ret=%d\n", ret); + if (!ret) + printf("%s;%" PRIu64 ";%s\n", sym.name, sym.offset, sym.module); + + } else if (!strcmp(in->cmd, "GET_KSYM_ADDR")) { + int ret; + char *name; + uint64_t addr; + + PARSE_STR(name); + + if (!ksym_cache) + ksym_cache = bcc_symcache_new(-1, NULL); + + ret = bcc_symcache_resolve_name(ksym_cache, NULL, name, &addr); + printf("GET_KSYM_ADDR: ret=%d\n", ret); + if (!ret) + printf("%" PRIu64 "\n", addr); + + } else if (!strcmp(in->cmd, "GET_USYM_NAME")) { + int ret, pid, demangle; + uint64_t addr; + struct bcc_symbol sym; + const char *name; + struct usym_cache *usym_cache = NULL; + + PARSE_INT(pid); + PARSE_UINT64(addr); + PARSE_INT(demangle); + + usym_cache = get_or_set_usym_cache(pid, usym_caches); + + if (demangle) + ret = bcc_symcache_resolve(usym_cache->cache, addr, &sym); + else + ret = bcc_symcache_resolve_no_demangle(usym_cache->cache, addr, &sym); + + printf("GET_USYM_NAME: ret=%d\n", ret); + if (!ret) { + if (demangle) + name = sym.demangle_name; + else + name = sym.name; + printf("%s;%" PRIu64 ";%s\n", name, sym.offset, sym.module); + } + bcc_symbol_free_demangle_name(&sym); + + } else if (!strcmp(in->cmd, "GET_USYM_ADDR")) { + int ret, pid; + char *name; + char *module; + uint64_t addr; + struct usym_cache *usym_cache = NULL; + + PARSE_INT(pid); + PARSE_STR(name); + PARSE_STR(module); + + usym_cache = get_or_set_usym_cache(pid, usym_caches); + + ret = bcc_symcache_resolve_name(usym_cache->cache, module, name, &addr); + printf("GET_USYM_ADDR: ret=%d\n", ret); + if (!ret) + printf("%" PRIu64 "\n", addr); + + } else { + invalid_command: + printf("Command not recognized\n"); + } + + printf("END_BPFD_OUTPUT\n"); + fflush(stdout); + + free_user_input(in); + in = NULL; + } + free_usym_caches(usym_caches); + return 0; +} diff --git a/src/cc/bpfd/bpfd.h b/src/cc/bpfd/bpfd.h new file mode 100644 index 000000000000..a3ca815e622e --- /dev/null +++ b/src/cc/bpfd/bpfd.h @@ -0,0 +1,68 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * This header is only supposed to be used by bpfd.c + * + * Copyright (C) 2017 Joel Fernandes + * + * 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 "base64.h" +#include "cmd_parsers.h" +#include "libbpf.h" +#include "utils.h" + +#define PARSE_INT(var) \ + { \ + int p = parse_int_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +#define PARSE_UINT(var) \ + { \ + int p = parse_uint_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +#define PARSE_UINT32(var) \ + { \ + int p = parse_uint32_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +#define PARSE_UINT64(var) \ + { \ + int p = parse_uint64_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +#define PARSE_ULL(var) \ + { \ + int p = parse_ull_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +#define PARSE_STR(var) \ + { \ + int p = parse_str_arg(in, arg_index++, &var); \ + if (p) \ + goto invalid_command; \ + } + +int bpf_remote_open_perf_buffer(int pid, int cpu, int page_cnt); +int remote_perf_reader_poll(int *fds, int num_readers, int timeout); diff --git a/src/cc/bpfd/cmd_parsers.c b/src/cc/bpfd/cmd_parsers.c new file mode 100644 index 000000000000..6aa760dd6f7d --- /dev/null +++ b/src/cc/bpfd/cmd_parsers.c @@ -0,0 +1,140 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * + * Copyright (C) 2018 Jazel Canseco + * + * 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 +#include +#include +#include + +#include "cmd_parsers.h" + +int count_num_tokens(const char *str) { + char *str_copy = NULL; + int num_tokens = 0; + char *token = NULL; + + str_copy = (char *)malloc(strlen(str) + 1); + strcpy(str_copy, str); + + for (token = strtok(str_copy, " "); token != NULL; + token = strtok(NULL, " ")) { + num_tokens++; + } + + free(str_copy); + return num_tokens; +} + +struct user_input *parse_user_input(const char *str) { + struct user_input *in = NULL; + char *str_copy = NULL, *token = NULL; + int arg_index = 0, num_tokens = 0; + + num_tokens = count_num_tokens(str); + + in = (struct user_input *)malloc(sizeof(struct user_input)); + in->num_args = num_tokens > 1 ? num_tokens - 1 : 0; + in->args = + in->num_args > 0 ? (char **)malloc(in->num_args * sizeof(char *)) : NULL; + + if (num_tokens == 0) { + in->cmd = NULL; + return in; + } + + str_copy = (char *)malloc(strlen(str) + 1); + strcpy(str_copy, str); + + token = strtok(str_copy, " "); + in->cmd = (char *)malloc(strlen(token) + 1); + strcpy(in->cmd, token); + + if (in->num_args > 0) { + while ((token = strtok(NULL, " "))) { + in->args[arg_index] = (char *)malloc(strlen(token) + 1); + strcpy(in->args[arg_index], token); + arg_index++; + } + } + + free(str_copy); + return in; +} + +void free_user_input(struct user_input *in) { + int i; + + if (!in) + return; + + if (in->cmd) + free(in->cmd); + + if (in->args) { + for (i = 0; i < in->num_args; i++) + free(in->args[i]); + + free(in->args); + } + + free(in); +} + +int parse_int_arg(const struct user_input *in, int index, int *val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + return !(sscanf(in->args[index], "%d", val) == 1); +} + +int parse_uint_arg(const struct user_input *in, int index, unsigned int *val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + return !(sscanf(in->args[index], "%u", val) == 1); +} + +int parse_uint32_arg(const struct user_input *in, int index, uint32_t *val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + return !(sscanf(in->args[index], "%" SCNu32 "", val) == 1); +} + +int parse_uint64_arg(const struct user_input *in, int index, uint64_t *val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + return !(sscanf(in->args[index], "%" SCNu64 "", val) == 1); +} + +int parse_ull_arg(const struct user_input *in, int index, + unsigned long long *val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + return !(sscanf(in->args[index], "%llu", val) == 1); +} + +int parse_str_arg(const struct user_input *in, int index, char **val) { + if (index < 0 || index > in->num_args - 1) + return -1; + + *val = in->args[index]; + return 0; +} diff --git a/src/cc/bpfd/cmd_parsers.h b/src/cc/bpfd/cmd_parsers.h new file mode 100644 index 000000000000..8938a164a532 --- /dev/null +++ b/src/cc/bpfd/cmd_parsers.h @@ -0,0 +1,52 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * + * Copyright (C) 2018 Jazel Canseco + * + * 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. + */ + +struct user_input { + char *cmd; + char **args; + int num_args; +}; + +/* + * Parses the string into a user_input struct object. + * The string is assumed to take the format of + * + * cmd arg1 arg2 arg3 ... argn + * + * If no cmd is provided (i.e. an empty string), the 'cmd' and 'args' fields are + * set to NULL. + * If a cmd is provided, but with no args, only the 'args' field is set to NULL. + */ +struct user_input *parse_user_input(const char *str); + +/* + * Frees user_input struct objects and their contents. + */ +void free_user_input(struct user_input *in); + +/* + * Functions for parsing arguments encapsulated by the user_input struct. + * Returns 0 on success. + */ +int parse_int_arg(const struct user_input *in, int index, int *val); +int parse_uint_arg(const struct user_input *in, int index, unsigned int *val); +int parse_uint32_arg(const struct user_input *in, int index, uint32_t *val); +int parse_uint64_arg(const struct user_input *in, int index, uint64_t *val); +int parse_ull_arg(const struct user_input *in, int index, + unsigned long long *val); +int parse_str_arg(const struct user_input *in, int index, char **val); diff --git a/src/cc/bpfd/remote_perf_reader.c b/src/cc/bpfd/remote_perf_reader.c new file mode 100644 index 000000000000..c25fdc1a5887 --- /dev/null +++ b/src/cc/bpfd/remote_perf_reader.c @@ -0,0 +1,98 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * Support for perf readers. + * + * Copyright (C) 2017 Joel Fernandes + * + * 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. + */ + +/* + * This file's functionality should be properly abstracted + * within libbpf.c and perf_reader.c. For now, duplicate the + * struct here + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bpfd.h" +#include "perf_reader.h" + +#define MAX_READERS 1024 + +struct perf_reader *remote_readers[MAX_READERS]; + +void remote_raw_reader_cb(void *cookie, void *raw, int size) { + struct perf_reader *reader = cookie; + char *raw_str; + + raw_str = malloc(size * 4); + + if (!base64_encode(raw, size, raw_str, size * 4)) + printf("raw_cb: b64 encode failed for reader fd=%d\n", reader->fd); + + printf("%d %d %s\n", reader->fd, size, raw_str); + + free(raw_str); +} + +int bpf_remote_open_perf_buffer(int pid, int cpu, int page_cnt) { + struct perf_reader *reader; + + reader = bpf_open_perf_buffer(remote_raw_reader_cb, NULL, NULL, pid, cpu, + page_cnt); + if (!reader) + return -1; + + reader->cb_cookie = reader; + remote_readers[reader->fd] = reader; + return reader->fd; +} + +int remote_perf_reader_poll(int *fds, int num_readers, int timeout) { + struct pollfd pfds[num_readers + 1]; + int i, fd; + + for (i = 0; i < num_readers; i++) { + fd = fds[i]; + if (!remote_readers[fd]) + continue; + pfds[i].fd = fd; + pfds[i].events = POLLIN; + } + + // Include stdin in the collection of file descriptors to poll. + // This is so that user input to bpfd via stdin can still be acted + // on immediately. Not doing this will lead to bpfd being unable to + // respond to user input until after poll() returns. + pfds[num_readers].fd = 0; + pfds[num_readers].events = POLLIN; + + if (poll(pfds, num_readers + 1, timeout) > 0) { + for (i = 0; i < num_readers + 1; i++) { + fd = pfds[i].fd; + if (fd != 0 && pfds[i].revents & POLLIN) + perf_reader_event_read(remote_readers[fd]); + } + } + + return 0; +} diff --git a/src/cc/bpfd/utils.c b/src/cc/bpfd/utils.c new file mode 100644 index 000000000000..0bc5f45ee060 --- /dev/null +++ b/src/cc/bpfd/utils.c @@ -0,0 +1,98 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * Common utility/helper functions. + * + * Copyright (C) 2017 Joel Fernandes + * + * 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 "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Read a file on the local fs to stdout */ +int cat_file(char *path) { + char buf[4096]; + int len, fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + printf("Open failed, ignoring\n"); + return fd; + } + + while ((len = read(fd, &buf, 4096)) > 0) + write(1, buf, len); + + close(fd); + + return 0; +} + +/* Read a tracefs file to stdout */ +int cat_tracefs_file(char *tracefs, char *fn) { + int res = 0; + + int buf_len = strlen(tracefs) + strlen(fn) + 2; // +2 for '/' and '\0' + char *tracef = (char *)malloc(buf_len); + snprintf(tracef, buf_len, "%s/%s", tracefs, fn); + + res = cat_file(tracef); + + free(tracef); + return res; +} + +int cat_comm_file(int pid) { + char commf[100]; + + snprintf(commf, 100, "/proc/%d/comm", pid); + + return cat_file(commf); +} + +int cat_dir(char *path, int dirs_only) { + DIR *dp; + struct dirent *ep; + + dp = opendir(path); + if (!dp) + return -1; + + while ((ep = readdir(dp))) { + struct stat st; + + if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) + continue; + + if (dirs_only) { + if (fstatat(dirfd(dp), ep->d_name, &st, 0) < 0) + continue; + + if (!S_ISDIR(st.st_mode)) + continue; + } + + printf("%s\n", ep->d_name); + } + closedir(dp); + + return 0; +} diff --git a/src/cc/bpfd/utils.h b/src/cc/bpfd/utils.h new file mode 100644 index 000000000000..4cd1a57cdd6f --- /dev/null +++ b/src/cc/bpfd/utils.h @@ -0,0 +1,23 @@ +/* + * BPFd (Berkeley Packet Filter daemon) + * Common utility/helper functions. + * + * Copyright (C) 2017 Joel Fernandes + * + * 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. + */ + +int cat_file(char *path); +int cat_tracefs_file(char *tracefs, char *fn); +int cat_comm_file(int pid); +int cat_dir(char *path, int dirs_only); diff --git a/src/cc/frontends/clang/CMakeLists.txt b/src/cc/frontends/clang/CMakeLists.txt index a6228fcefeb1..105278ce9391 100644 --- a/src/cc/frontends/clang/CMakeLists.txt +++ b/src/cc/frontends/clang/CMakeLists.txt @@ -10,3 +10,4 @@ if(DEFINED BCC_BACKUP_COMPILE) endif() add_library(clang_frontend STATIC loader.cc b_frontend_action.cc tp_frontend_action.cc kbuild_helper.cc ../../common.cc) +target_link_libraries(clang_frontend bcc-static) diff --git a/src/cc/frontends/clang/b_frontend_action.cc b/src/cc/frontends/clang/b_frontend_action.cc index 6cb00138e14e..d161fbd1ea32 100644 --- a/src/cc/frontends/clang/b_frontend_action.cc +++ b/src/cc/frontends/clang/b_frontend_action.cc @@ -790,10 +790,22 @@ bool BTypeVisitor::VisitVarDecl(VarDecl *Decl) { return false; } + if (bpf_create_map_cb) { + struct bpf_create_map_args args; + args.type = map_type; + args.name = (char *)table.name.c_str(); + args.key_size = table.key_size; + args.value_size = table.leaf_size; + args.max_entries = table.max_entries; + args.map_flags = table.flags; + + table.fd = bpf_create_map_cb(&args); + } else { + table.fd = + bpf_create_map(map_type, table.name.c_str(), table.key_size, + table.leaf_size, table.max_entries, table.flags); + } table.type = map_type; - table.fd = bpf_create_map(map_type, table.name.c_str(), - table.key_size, table.leaf_size, - table.max_entries, table.flags); } if (table.fd < 0) { error(Decl->getLocStart(), "could not open bpf map: %0\nis %1 map type enabled in your kernel?") << diff --git a/src/cc/libbpf.h b/src/cc/libbpf.h index e59d48ab6559..66bb79a12561 100644 --- a/src/cc/libbpf.h +++ b/src/cc/libbpf.h @@ -24,6 +24,18 @@ extern "C" { #endif +typedef int (*bpf_create_map_cb_t)(void *data); +struct bpf_create_map_args { + unsigned int type; + char *name; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; +}; + +extern bpf_create_map_cb_t bpf_create_map_cb; + enum bpf_probe_attach_type { BPF_PROBE_ENTRY, BPF_PROBE_RETURN diff --git a/src/cc/perf_reader.c b/src/cc/perf_reader.c index 91817bcad77c..b9c33fc848e3 100644 --- a/src/cc/perf_reader.c +++ b/src/cc/perf_reader.c @@ -36,20 +36,6 @@ enum { RB_USED_IN_READ = 2, // used in read }; -struct perf_reader { - perf_reader_raw_cb raw_cb; - perf_reader_lost_cb lost_cb; - void *cb_cookie; // to be returned in the cb - void *buf; // for keeping segmented data - size_t buf_size; - void *base; - int rb_use_state; - pid_t rb_read_tid; - int page_size; - int page_cnt; - int fd; -}; - struct perf_reader * perf_reader_new(perf_reader_raw_cb raw_cb, perf_reader_lost_cb lost_cb, void *cb_cookie, int page_cnt) { diff --git a/src/cc/perf_reader.h b/src/cc/perf_reader.h index dbe9cfb8dc8d..783021c9fe63 100644 --- a/src/cc/perf_reader.h +++ b/src/cc/perf_reader.h @@ -23,7 +23,19 @@ extern "C" { #endif -struct perf_reader; +struct perf_reader { + perf_reader_raw_cb raw_cb; + perf_reader_lost_cb lost_cb; + void *cb_cookie; // to be returned in the cb + void *buf; // for keeping segmented data + size_t buf_size; + void *base; + int rb_use_state; + pid_t rb_read_tid; + int page_size; + int page_cnt; + int fd; +}; struct perf_reader * perf_reader_new(perf_reader_raw_cb raw_cb, perf_reader_lost_cb lost_cb, diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 27cbb45772cc..46bfe85dbb40 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -24,10 +24,11 @@ import sys basestring = (unicode if sys.version_info[0] < 3 else str) -from .libbcc import lib, bcc_symbol, bcc_symbol_option, _SYM_CB_TYPE +from .libbcc import lib, bcc_symbol, bcc_symbol_option, _SYM_CB_TYPE, _MAP_CB_TYPE from .table import Table, PerfEventArray from .perf import Perf from .utils import get_online_cpus, printb, _assert_is_bytes, ArgString +from .remote import libremote, remote_utils _probe_limit = 1000 _num_open_probes = 0 @@ -126,6 +127,14 @@ class PerfSWConfig: DUMMY = 9 BPF_OUTPUT = 10 +class BpfCreateMapArgs(ct.Structure): + _fields_ = [("type", ct.c_uint), + ("name", ct.c_char_p), + ("key_size", ct.c_uint), + ("value_size", ct.c_uint), + ("max_entries", ct.c_uint), + ("map_flags", ct.c_uint)] + class BPF(object): # From bpf_prog_type in uapi/linux/bpf.h SOCKET_FILTER = 1 @@ -142,6 +151,7 @@ class BPF(object): XDP_PASS = 2 XDP_TX = 3 + _libremote = None _probe_repl = re.compile(b"[^a-zA-Z0-9_]") _sym_caches = {} @@ -244,6 +254,16 @@ def is_exe(fpath): return exe_file return None + @staticmethod + def comm_for_pid(pid): + if BPF._libremote: + return BPF._libremote.comm_for_pid(pid) + else: + try: + return open("/proc/%d/comm" % pid, "rb").read().strip() + except Exception: + return b"[unknown]" + def __init__(self, src_file=b"", hdr_file=b"", text=None, debug=0, cflags=[], usdt_contexts=[]): """Create a new BPF module with the given source code. @@ -260,6 +280,9 @@ def __init__(self, src_file=b"", hdr_file=b"", text=None, debug=0, See "Debug flags" for explanation """ + if BPF._should_run_on_remote_target(): + BPF._libremote = BPF._open_connection_to_remote_target() + src_file = _assert_is_bytes(src_file) hdr_file = _assert_is_bytes(hdr_file) text = _assert_is_bytes(text) @@ -292,7 +315,8 @@ def __init__(self, src_file=b"", hdr_file=b"", text=None, debug=0, if text: self.module = lib.bpf_module_create_c_from_string(text, - self.debug, cflags_array, len(cflags_array)) + self.debug, cflags_array, len(cflags_array), + _MAP_CB_TYPE(BPF._bpf_create_map_cb)) if not self.module: raise Exception("Failed to compile BPF text:\n%s" % text) else: @@ -303,7 +327,8 @@ def __init__(self, src_file=b"", hdr_file=b"", text=None, debug=0, self.debug) else: self.module = lib.bpf_module_create_c(src_file, self.debug, - cflags_array, len(cflags_array)) + cflags_array, len(cflags_array), + _MAP_CB_TYPE(BPF._bpf_create_map_cb)) if not self.module: raise Exception("Failed to compile BPF module %s" % src_file) @@ -314,6 +339,14 @@ def __init__(self, src_file=b"", hdr_file=b"", text=None, debug=0, # they will be loaded and attached here. self._trace_autoload() + @staticmethod + def _should_run_on_remote_target(): + return "BCC_REMOTE" in os.environ + + @staticmethod + def _open_connection_to_remote_target(): + return libremote.LibRemote(os.environ.get("BCC_REMOTE"), os.environ.get("BCC_REMOTE_ARGS")) + def load_funcs(self, prog_type=KPROBE): """load_funcs(prog_type=KPROBE) @@ -338,21 +371,30 @@ def load_func(self, func_name, prog_type): log_level = 2 elif (self.debug & DEBUG_BPF): log_level = 1 - fd = lib.bpf_prog_load(prog_type, func_name, - lib.bpf_function_start(self.module, func_name), - lib.bpf_function_size(self.module, func_name), - lib.bpf_module_license(self.module), - lib.bpf_module_kern_version(self.module), - log_level, None, 0); - - if fd < 0: - atexit.register(self.donothing) - if ct.get_errno() == errno.EPERM: - raise Exception("Need super-user privileges to run") - errstr = os.strerror(ct.get_errno()) - raise Exception("Failed to load BPF program %s: %s" % - (func_name, errstr)) + func_start = lib.bpf_function_start(self.module, func_name) + func_size = lib.bpf_function_size(self.module, func_name) + func_str = ct.string_at(func_start, func_size) + license_str = ct.string_at(lib.bpf_module_license(self.module)) + kern_version = lib.bpf_module_kern_version(self.module) + + if BPF._libremote: + fd = BPF._libremote.bpf_prog_load(prog_type, func_name, func_str, + license_str, kern_version) + if fd < 0: + raise Exception("Failed to load BPF program from remote") + else: + fd = lib.bpf_prog_load(prog_type, func_name, func_start, + func_size, license_str, kern_version, + log_level, None, 0); + if fd < 0: + atexit.register(self.donothing) + if ct.get_errno() == errno.EPERM: + raise Exception("Need super-user privileges to run") + + errstr = os.strerror(ct.get_errno()) + raise Exception("Failed to load BPF program %s: %s" % + (func_name, errstr)) fn = BPF.Function(self, func_name, fd) self.funcs[func_name] = fn @@ -441,7 +483,8 @@ def get_table(self, name, keytype=None, leaftype=None, reducer=None): if not leaf_desc: raise Exception("Failed to load BPF Table %s leaf desc" % name) leaftype = BPF._decode_table_type(json.loads(leaf_desc)) - return Table(self, map_id, map_fd, keytype, leaftype, reducer=reducer) + return Table(self, map_id, map_fd, keytype, leaftype, reducer=reducer, + libremote=BPF._libremote) def __getitem__(self, key): if key not in self.tables: @@ -460,6 +503,20 @@ def __delitem__(self, key): def __iter__(self): return self.tables.__iter__() + @staticmethod + def _bpf_create_map_cb(data): + args = ct.cast(data, ct.POINTER(BpfCreateMapArgs)).contents + if BPF._libremote: + remote_utils.log("bpf_create_map cb: got map name: {}, type {}" + .format(args.name, type(args.name))) + return BPF._libremote.bpf_create_map(args.type, args.name, + args.key_size, args.value_size, + args.max_entries, args.map_flags) + else: + return lib.bpf_create_map(args.type, args.name, args.key_size, + args.value_size, args.max_entries, + args.map_flags) + @staticmethod def attach_raw_socket(fn, dev): dev = _assert_is_bytes(dev) @@ -478,26 +535,39 @@ def attach_raw_socket(fn, dev): @staticmethod def get_kprobe_functions(event_re): - with open("%s/../kprobes/blacklist" % TRACEFS, "rb") as blacklist_f: - blacklist = set([line.rstrip().split()[1] for line in blacklist_f]) - fns = [] + if BPF._libremote: + blacklist = BPF._libremote.kprobes_blacklist(TRACEFS) + else: + with open("%s/../kprobes/blacklist" % TRACEFS, "rb") as blacklist_f: + blacklist = set([line.rstrip().split()[1] for line in blacklist_f]) - found_stext = False - with open("/proc/kallsyms", "rb") as avail_file: - for line in avail_file: - (_, t, fn) = line.rstrip().split()[:3] - if found_stext is False: - if fn == b'_stext': - found_stext = True - continue - - if fn == b'_etext': - break - if (t.lower() in [b't', b'w']) and re.match(event_re, fn) \ - and fn not in blacklist: - fns.append(fn) + if BPF._libremote: + kallsyms = BPF._libremote.kallsyms() + fns = BPF._get_kprobe_funcs_from_kallsyms(kallsyms) + else: + with open("/proc/kallsyms", "rb") as kallsyms: + fns = BPF._get_kprobe_funcs_from_kallsyms(kallsyms) + + fns = [fn for fn in fns if (re.match(event_re, fn) and fn not in blacklist)] return set(fns) # Some functions may appear more than once + @staticmethod + def _get_kprobe_funcs_from_kallsyms(kallsyms): + fns = [] + found_stext = False + for line in kallsyms: + (_, t, fn) = line.rstrip().split()[:3] + if found_stext is False: + if fn == b'_stext': + found_stext = True + continue + + if fn == b'_etext': + break + if t.lower() in [b't', b'w']: + fns.append(fn) + return fns + def _check_probe_quota(self, num_new_probes): global _num_open_probes if _num_open_probes + num_new_probes > _probe_limit: @@ -523,6 +593,13 @@ def _del_uprobe_fd(self, name): del self.uprobe_fds[name] _num_open_probes -= 1 + @staticmethod + def _bpf_close_perf_event_fd(fd): + if BPF._libremote: + return BPF._libremote.bpf_close_perf_event_fd(fd) + + return lib.bpf_close_perf_event_fd(fd) + def attach_kprobe(self, event=b"", fn_name=b"", event_re=b""): event = _assert_is_bytes(event) fn_name = _assert_is_bytes(fn_name) @@ -542,6 +619,14 @@ def attach_kprobe(self, event=b"", fn_name=b"", event_re=b""): self._check_probe_quota(1) fn = self.load_func(fn_name, BPF.KPROBE) ev_name = b"p_" + event.replace(b"+", b"_").replace(b".", b"_") + + if BPF._libremote: + fd = BPF._libremote.bpf_attach_kprobe(fn.fd, 0, ev_name, event) + if fd < 0: + raise Exception("Failed to attach BPF to kprobe") + self._add_kprobe_fd(ev_name, fd) + return self + fd = lib.bpf_attach_kprobe(fn.fd, 0, ev_name, event) if fd < 0: raise Exception("Failed to attach BPF to kprobe") @@ -565,6 +650,14 @@ def attach_kretprobe(self, event=b"", fn_name=b"", event_re=b""): self._check_probe_quota(1) fn = self.load_func(fn_name, BPF.KPROBE) ev_name = b"r_" + event.replace(b"+", b"_").replace(b".", b"_") + + if BPF._libremote: + fd = BPF._libremote.bpf_attach_kprobe(fn.fd, 1, ev_name, event) + if fd < 0: + raise Exception("Failed to attach BPF to kprobe") + self._add_kprobe_fd(ev_name, fd) + return self + fd = lib.bpf_attach_kprobe(fn.fd, 1, ev_name, event) if fd < 0: raise Exception("Failed to attach BPF to kretprobe") @@ -574,10 +667,15 @@ def attach_kretprobe(self, event=b"", fn_name=b"", event_re=b""): def detach_kprobe_event(self, ev_name): if ev_name not in self.kprobe_fds: raise Exception("Kprobe %s is not attached" % event) - res = lib.bpf_close_perf_event_fd(self.kprobe_fds[ev_name]) + + res = BPF._bpf_close_perf_event_fd(self.kprobe_fds[ev_name]) if res < 0: raise Exception("Failed to close kprobe FD") - res = lib.bpf_detach_kprobe(ev_name) + + if BPF._libremote: + res = BPF._libremote.bpf_detach_kprobe(ev_name) + else: + res = lib.bpf_detach_kprobe(ev_name) if res < 0: raise Exception("Failed to detach BPF from kprobe") self._del_kprobe_fd(ev_name) @@ -657,6 +755,14 @@ def find_library(libname): @staticmethod def get_tracepoints(tp_re): results = [] + if BPF._libremote: + for category in BPF._libremote.get_trace_events_categories(TRACEFS): + for event in BPF_.libremote.get_trace_events(TRACEFS, category): + tp = ("%s:%s" % (category, event)) + if re.match(tp_re, tp): + results.append(tp) + return results + events_dir = os.path.join(TRACEFS, "events") for category in os.listdir(events_dir): cat_dir = os.path.join(events_dir, category) @@ -706,6 +812,14 @@ def attach_tracepoint(self, tp=b"", tp_re=b"", fn_name=b""): fn = self.load_func(fn_name, BPF.TRACEPOINT) (tp_category, tp_name) = tp.split(b':') + + if BPF._libremote: + fd = BPF._libremote.bpf_attach_tracepoint(fn.fd, tp_category, tp_name) + if fd < 0: + raise Exception("Failed to attach BPF to tracepoint") + self.tracepoint_fds[tp] = fd + return self + fd = lib.bpf_attach_tracepoint(fn.fd, tp_category, tp_name) if fd < 0: raise Exception("Failed to attach BPF to tracepoint") @@ -724,19 +838,28 @@ def detach_tracepoint(self, tp=b""): tp = _assert_is_bytes(tp) if tp not in self.tracepoint_fds: raise Exception("Tracepoint %s is not attached" % tp) - res = lib.bpf_close_perf_event_fd(self.tracepoint_fds[tp]) + res = BPF._bpf_close_perf_event_fd(self.tracepoint_fds[tp]) if res < 0: raise Exception("Failed to detach BPF from tracepoint") + (tp_category, tp_name) = tp.split(b':') - res = lib.bpf_detach_tracepoint(tp_category, tp_name) + if BPF._libremote: + res = BPF._libremote.bpf_detach_tracepoint(tp_category, tp_name) + else: + res = lib.bpf_detach_tracepoint(tp_category, tp_name) if res < 0: raise Exception("Failed to detach BPF from tracepoint") + del self.tracepoint_fds[tp] def _attach_perf_event(self, progfd, ev_type, ev_config, sample_period, sample_freq, pid, cpu, group_fd): - res = lib.bpf_attach_perf_event(progfd, ev_type, ev_config, - sample_period, sample_freq, pid, cpu, group_fd) + if BPF._libremote: + res = BPF._libremote.bpf_attach_perf_event(progfd, ev_type, + ev_config, sample_period, sample_freq, pid, cpu, group_fd) + else: + res = lib.bpf_attach_perf_event(progfd, ev_type, ev_config, + sample_period, sample_freq, pid, cpu, group_fd) if res < 0: raise Exception("Failed to attach BPF to perf event") return res @@ -764,7 +887,7 @@ def detach_perf_event(self, ev_type=-1, ev_config=-1): res = 0 for fd in fds.values(): - res = lib.bpf_close_perf_event_fd(fd) or res + res = BPF._bpf_close_perf_event_fd(fd) or res if res != 0: raise Exception("Failed to detach BPF from perf event") del self.open_perf_events[(ev_type, ev_config)] @@ -852,6 +975,14 @@ def attach_uprobe(self, name=b"", sym=b"", sym_re=b"", addr=None, self._check_probe_quota(1) fn = self.load_func(fn_name, BPF.KPROBE) ev_name = self._get_uprobe_evname(b"p", path, addr, pid) + + if BPF._libremote: + fd = BPF._libremote.bpf_attach_uprobe(fn.fd, 0, ev_name, path, addr, pid) + if fd < 0: + raise Exception("Failed to attach BPF to uprobe") + self._add_uprobe_fd(ev_name, fd) + return self + fd = lib.bpf_attach_uprobe(fn.fd, 0, ev_name, path, addr, pid) if fd < 0: raise Exception("Failed to attach BPF to uprobe") @@ -884,6 +1015,14 @@ def attach_uretprobe(self, name=b"", sym=b"", sym_re=b"", addr=None, self._check_probe_quota(1) fn = self.load_func(fn_name, BPF.KPROBE) ev_name = self._get_uprobe_evname(b"r", path, addr, pid) + + if BPF._libremote: + fd = BPF._libremote.bpf_attach_uprobe(fn.fd, 1, ev_name, path, addr, pid) + if fd < 0 : + raise Exception("Failed to attach BPF to uprobe") + self._add_uprobe_fd(ev_name, fd) + return self + fd = lib.bpf_attach_uprobe(fn.fd, 1, ev_name, path, addr, pid) if fd < 0: raise Exception("Failed to attach BPF to uretprobe") @@ -893,10 +1032,15 @@ def attach_uretprobe(self, name=b"", sym=b"", sym_re=b"", addr=None, def detach_uprobe_event(self, ev_name): if ev_name not in self.uprobe_fds: raise Exception("Uprobe %s is not attached" % ev_name) - res = lib.bpf_close_perf_event_fd(self.uprobe_fds[ev_name]) + + res = BPF._bpf_close_perf_event_fd(self.uprobe_fds[ev_name]) if res < 0: raise Exception("Failed to detach BPF from uprobe") - res = lib.bpf_detach_uprobe(ev_name) + + if BPF._libremote: + res = BPF._libremote.bpf_detach_uprobe(ev_name) + else: + res = lib.bpf_detach_uprobe(ev_name) if res < 0: raise Exception("Failed to detach BPF from uprobe") self._del_uprobe_fd(ev_name) @@ -1057,7 +1201,10 @@ def sym(addr, pid, show_module=False, show_offset=False, demangle=True): Example output when both show_module and show_offset are False: "start_thread" """ - name, offset, module = BPF._sym_cache(pid).resolve(addr, demangle) + if BPF._libremote: + name, offset, module = BPF._libremote.sym(pid, addr, demangle) + else: + name, offset, module = BPF._sym_cache(pid).resolve(addr, demangle) offset = b"+0x%x" % offset if show_offset and name is not None else b"" name = name or b"[unknown]" name = name + offset @@ -1085,7 +1232,10 @@ def ksymname(name): Translate a kernel name into an address. This is the reverse of ksym. Returns -1 when the function name is unknown.""" - return BPF._sym_cache(-1).resolve_name(None, name) + if BPF._libremote: + return BPF._libremote.ksymname(name) + else: + return BPF._sym_cache(-1).resolve_name(None, name) def num_open_kprobes(self): """num_open_kprobes() @@ -1116,9 +1266,25 @@ def perf_buffer_poll(self, timeout = -1): provided when calling open_perf_buffer for each entry. """ try: + if BPF._libremote: + fd_callbacks = [] + for k, v in self.perf_buffers.iteritems(): + # Only support polling for per-cpu perf buffers + # { (PerfEventArray-Obj, cpu) -> fd } + if v and type(k) == tuple: + t_id = k[0] + t_obj = ct.cast(t_id, ct.py_object).value + cpu = k[1] + cbs = t_obj._cbs[cpu] + fd_callbacks.append((v, cbs)) + if fd_callbacks: + BPF._libremote.perf_reader_poll(fd_callbacks, timeout) + return + readers = (ct.c_void_p * len(self.perf_buffers))() for i, v in enumerate(self.perf_buffers.values()): readers[i] = v + lib.perf_reader_poll(len(readers), readers, timeout) except KeyboardInterrupt: exit() diff --git a/src/python/bcc/libbcc.py b/src/python/bcc/libbcc.py index fd9f72b18c1b..e16730d9a27d 100644 --- a/src/python/bcc/libbcc.py +++ b/src/python/bcc/libbcc.py @@ -17,14 +17,15 @@ lib = ct.CDLL("libbcc.so.0", use_errno=True) # keep in sync with bpf_common.h +_MAP_CB_TYPE = ct.CFUNCTYPE(ct.c_int, ct.c_void_p) lib.bpf_module_create_b.restype = ct.c_void_p lib.bpf_module_create_b.argtypes = [ct.c_char_p, ct.c_char_p, ct.c_uint] lib.bpf_module_create_c.restype = ct.c_void_p lib.bpf_module_create_c.argtypes = [ct.c_char_p, ct.c_uint, - ct.POINTER(ct.c_char_p), ct.c_int] + ct.POINTER(ct.c_char_p), ct.c_int, _MAP_CB_TYPE] lib.bpf_module_create_c_from_string.restype = ct.c_void_p lib.bpf_module_create_c_from_string.argtypes = [ct.c_char_p, ct.c_uint, - ct.POINTER(ct.c_char_p), ct.c_int] + ct.POINTER(ct.c_char_p), ct.c_int, _MAP_CB_TYPE] lib.bpf_module_destroy.restype = None lib.bpf_module_destroy.argtypes = [ct.c_void_p] lib.bpf_module_license.restype = ct.c_char_p diff --git a/src/python/bcc/remote/__init__.py b/src/python/bcc/remote/__init__.py new file mode 100644 index 000000000000..f4852d0c697f --- /dev/null +++ b/src/python/bcc/remote/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Joel Fernandes +# +# 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. diff --git a/src/python/bcc/remote/adb.py b/src/python/bcc/remote/adb.py new file mode 100644 index 000000000000..2ae327a4d6ec --- /dev/null +++ b/src/python/bcc/remote/adb.py @@ -0,0 +1,44 @@ +# Copyright 2017 Joel Fernandes +# +# 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. + +import pexpect as pe +import time +import os + +from .shell import ShellRemote + +class AdbRemote(ShellRemote): + def __init__(self, cmd=None): + """ + Create a connection by spawning bpfd and communicating + with it using the spawned bpfd process's stdin and stdout. + + :param cmd: Command to execute for bpfd. If not specified, + then we default to search for bpfd in the path. + :type cmd: str + """ + + # adb server is expected to be connected, either + # through USB or the network + self.client = pe.spawn("adb shell") + time.sleep(2) + + if cmd == None: + cmd = '/data/bpfd' + + self.client.sendline("su") + self.client.readline() + + self.client.sendline(cmd) + self.client.expect('STARTED_BPFD') diff --git a/src/python/bcc/remote/base.py b/src/python/bcc/remote/base.py new file mode 100644 index 000000000000..edb346303a1d --- /dev/null +++ b/src/python/bcc/remote/base.py @@ -0,0 +1,55 @@ +# Copyright 2017 Joel Fernandes +# +# 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. + +class BccRemote(object): + """ + Abstract Class for BccRemote connection classes + This defines the API implementation for remotes such + as networking or shell based remote bcc connections + """ + + def __init__(self, arg): + """ + Initialize of remote connection + + A remote class should override this and provide an __init__ + function that establishes a connection to a remote, or + raises an error. + + :param arg: An argument string specific to the remote + :type arg: str + """ + pass + + def send_command(self, cmd): + """ + Abstract function to send a command to a remote + + :param cmd: Command to send. + :type cmd: str + + :return: Output of the command. + :rtype: List of strings, one for each line of command output. + """ + raise NotImplementedError('subclasses must override class!') + + def close_connection(self): + """ + Abstraction function to close a remote connection. + + Remote classes should override this function and tear down + an initialized remote connection + """ + raise NotImplementedError('subclasses must override class!') + diff --git a/src/python/bcc/remote/libremote.py b/src/python/bcc/remote/libremote.py new file mode 100644 index 000000000000..012d50df7e74 --- /dev/null +++ b/src/python/bcc/remote/libremote.py @@ -0,0 +1,318 @@ +# Copyright 2017 Joel Fernandes +# Module to establish and maintain a remote connection +# +# 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. + +import base64 +import ctypes as ct +import re +import sys + +from .shell import ShellRemote +from .adb import AdbRemote + +def get_remote_cls(cls_name): + cls_name = cls_name.capitalize() + 'Remote' + cls = globals()[cls_name] + return cls + +class LibRemote(object): + def __init__(self, remote_name, remote_arg=None): + # Get the Remote class object + cls = get_remote_cls(remote_name) + + # Cache of maps, format: map_cache[map_fd] = { 'key1': 'value1', .. } + self.nkey_cache = {} + self.map_cache = {} + # Has the map been fully dumped once before the last delete/clear ? + # format: { map_fd: [True|False] } + self.map_dumped = {} + + # Create the remote connection + self.remote = cls(remote_arg) + + def _remote_send_command(self, cmd): + """ + Return: a tuple containing return code and output list. + """ + ret = self.remote.send_command(cmd) + if not ret: + return (-1, []) + + if ret[0].startswith('Command not recognized'): + print('Command not recognized! cmd: {}'.format(cmd)) + return (-1, []) + + if ret[0].startswith('Open failed, ignoring'): + return (-1, []) + + # Assume success if first list element doesn't have ret= + if not 'ret=' in ret[0]: + return (0, ret) + + m = re.search("ret=(\-?\d+)", ret[0]) + if m == None: + print('Bad return string for cmd {}'.format(cmd)) + return (-1, []) + + return (int(m.group(1)), ret) + + def _invalidate_map_cache(self, map_fd): + self.map_cache[map_fd] = {} + self.nkey_cache[map_fd] = {} + self.map_dumped[map_fd] = {} + + def kallsyms(self): + cmd = "GET_KALLSYMS" + ret = self._remote_send_command(cmd) + return ret[0] if ret[0] < 0 else ret[1] + + def kprobes_blacklist(self, tracefs): + cmd = "GET_KPROBES_BLACKLIST {}".format(tracefs) + ret = self._remote_send_command(cmd) + return ret[0] if ret[0] < 0 else ret[1] + + def get_trace_events(self, tracefs, cat): + cmd = "GET_TRACE_EVENTS {} {}".format(tracefs, cat) + ret = self._remote_send_command(cmd) + return ret[0] if ret[0] < 0 else ret[1] + + def get_trace_events_categories(self, tracefs): + cmd = "GET_TRACE_EVENTS_CATEGORIES {}".format(tracefs) + ret = self._remote_send_command(cmd) + return ret[0] if ret[0] < 0 else ret[1] + + def comm_for_pid(self, pid): + cmd = "COMM_FOR_PID {}".format(pid) + ret = self._remote_send_command(cmd) + + ret_code = ret[0] + if ret_code < 0: + return "[unknown]" + else: + comm = ret[1][0] + return comm + + def bpf_attach_tracepoint(self, fd, cat, tp_name): + cmd = "BPF_ATTACH_TRACEPOINT {} {} {}".format(fd, cat, tp_name) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_detach_tracepoint(self, tp_category, tp_name): + cmd = "BPF_DETACH_TRACEPOINT {} {}".format(tp_category, tp_name) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_attach_kprobe(self, fd, t, evname, fnname): + cmd = "BPF_ATTACH_KPROBE {} {} {} {}".format(fd, t, evname, fnname) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_detach_kprobe(self, evname): + cmd = "BPF_DETACH_KPROBE {}".format(evname) + ret = self._remote_send_command(cmd) + return 0 + + def bpf_attach_uprobe(self, fd, t, evname, binpath, offset, pid): + cmd = "BPF_ATTACH_UPROBE {} {} {} {} {} {}".format(fd, t, evname, binpath, offset, pid) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_detach_uprobe(self, evname): + cmd = "BPF_DETACH_UPROBE {}".format(evname) + ret = self._remote_send_command(cmd) + return 0 + + def bpf_prog_load(self, prog_type, name, func_str, license_str, kern_version): + cmd = "BPF_PROG_LOAD {} {} {} {} {} {}".format(prog_type, name, len(func_str), + license_str, kern_version, base64.b64encode(func_str)) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_create_map(self, map_type, name, key_size, leaf_size, max_entries, + flags): + cmd = "BPF_CREATE_MAP {} {} {} {} {} {}".format(map_type, name, key_size, + leaf_size, max_entries, flags) + ret = self._remote_send_command(cmd) + + if ret[0] > 0: + self.map_cache[ret[0]] = {} + self.nkey_cache[ret[0]] = {} + return ret[0] + + def bpf_update_elem(self, map_fd, kstr, klen, lstr, llen, flags): + cmd = "BPF_UPDATE_ELEM {} {} {} {} {} {}".format(map_fd, kstr, klen, + lstr, llen, flags) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_lookup_elem(self, map_fd, kstr, klen, llen): + if map_fd in self.map_cache: + if kstr in self.map_cache[map_fd]: + return (0, [self.map_cache[map_fd][kstr]]) + + # Some maps like StackTrace may not trigger a get_first_key before lookup + # since the keys can be obtained through other maps (like counts in offcputime) + # Force a get_first_key so that the entire map is cached. + if map_fd not in self.map_dumped or self.map_dumped[map_fd] == False: + self.bpf_get_first_key(map_fd, klen, llen, dump_all=True) + + cmd = "BPF_LOOKUP_ELEM {} {} {} {}".format(map_fd, kstr, klen, llen) + ret = self._remote_send_command(cmd) + return ret + + def bpf_open_perf_buffer(self, pid, cpu, page_cnt): + cmd = "BPF_OPEN_PERF_BUFFER {} {} {}".format(pid, cpu, page_cnt) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_get_first_key(self, map_fd, klen, vlen, dump_all=True): + cmd = "BPF_GET_FIRST_KEY {} {} {} {}".format(map_fd, klen, vlen, 1 if dump_all else 0) + ret = self._remote_send_command(cmd) + + if not dump_all or ret[0] < 0: + return ret + + # bpfd will dump the entire map on first get key so it can be + # cached for future use + key_values = ret[1] + first_key = key_values[0] + + it = iter(key_values) + prev_key = None + for i in it: + key = i + if not key: + continue + value = next(it) + self.map_cache[map_fd][key] = value + if prev_key: + self.nkey_cache[map_fd][prev_key] = key + prev_key = key + + self.map_dumped[map_fd] = True + + return (0, [first_key]) + + def bpf_get_next_key(self, map_fd, kstr, klen): + if map_fd in self.nkey_cache: + if kstr in self.nkey_cache[map_fd]: + return (0, [self.nkey_cache[map_fd][kstr]]) + + cmd = "BPF_GET_NEXT_KEY {} {} {}".format(map_fd, kstr, klen) + ret = self._remote_send_command(cmd) + return ret + + def bpf_delete_elem(self, map_fd, kstr, klen): + cmd = "BPF_DELETE_ELEM {} {} {}".format(map_fd, kstr, klen) + ret = self._remote_send_command(cmd) + self._invalidate_map_cache(map_fd) + return ret[0] + + def bpf_clear_map(self, map_fd, klen): + cmd = "BPF_CLEAR_MAP {} {}".format(map_fd, klen) + ret = self._remote_send_command(cmd) + self._invalidate_map_cache(map_fd) + return ret[0] + + def perf_reader_poll(self, fd_callbacks, timeout): + cmd = "" + fd_cb_dict = {} + for f in fd_callbacks: + cmd += " {}".format(f[0]) + fd_cb_dict[f[0]] = f[1] + cmd = "PERF_READER_POLL {} {}".format(timeout, len(fd_callbacks)) + cmd + ret = self._remote_send_command(cmd) + if ret[0] < 0: + return ret[0] + + for out in ret[1]: + # Format: + (fd, size, data_str) = out.split(" ") + fd = int(fd) + size = int(size) + + data_bin = ct.c_char_p(base64.b64decode(data_str)) + data_bin = ct.cast(data_bin, ct.c_void_p) + cbs = fd_cb_dict[fd] + + raw_cb = cbs[0] + lost_cb = cbs[1] + + raw_cb(ct.cast(id(self), ct.py_object), data_bin, ct.c_int(size)) + + def bpf_close_perf_event_fd(self, fd): + cmd = "BPF_CLOSE_PERF_EVENT_FD {}".format(fd) + ret = self._remote_send_command(cmd) + return ret[0] + + def sym(self, pid, addr, demangle=True): + if pid < 0: + return self.ksym(addr) + else: + return self.usym(pid, addr, demangle) + + def ksym(self, addr): + cmd = "GET_KSYM_NAME {}".format(addr) + ret = self._remote_send_command(cmd) + + ret_code = ret[0] + if ret_code < 0: + return None, addr, None + else: + name, offset, module = ret[1][1].split(";") + return name, offset, module + + def ksymname(self, name): + cmd = "GET_KSYM_ADDR {}".format(name) + ret = self._remote_send_command(cmd) + + ret_code = ret[0] + if ret_code < 0: + return -1 + else: + addr = ret[1][1] + return addr + + def usym(self, pid, addr, demangle=True): + cmd = "GET_USYM_NAME {} {} {}".format(pid, addr, 1 if demangle else 0) + ret = self._remote_send_command(cmd) + + ret_code = ret[0] + if ret_code < 0: + return None, addr, None + else: + name, offset, module = ret[1][1].split(";") + return name, offset, module + + def usymname(self, pid, name, module): + cmd = "GET_USYM_ADDR {} {} {}".format(pid, name, module) + ret = self._remote_send_command(cmd) + + ret_code = ret[0] + if ret_code < 0: + return -1 + else: + addr = ret[1][1] + return addr + + def bpf_attach_perf_event(self, progfd, ev_type, ev_config, sample_period, + sample_freq, pid, cpu, group_fd): + cmd = "BPF_ATTACH_PERF_EVENT {} {} {} {} {} {} {} {}".format(progfd, + ev_type, ev_config, sample_period, sample_freq, pid, cpu, group_fd) + ret = self._remote_send_command(cmd) + return ret[0] + + def close_connection(self): + self.remote.close_connection() + diff --git a/src/python/bcc/remote/remote_utils.py b/src/python/bcc/remote/remote_utils.py new file mode 100644 index 000000000000..4611983740d4 --- /dev/null +++ b/src/python/bcc/remote/remote_utils.py @@ -0,0 +1,21 @@ +# Copyright 2017 Jazel Canseco +# Module to establish and maintain a remote connection +# +# 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. + +import os + +# For debugging +def log(msg): + if os.environ.get('BCC_REMOTE_DEBUG') == '1': + print(msg) diff --git a/src/python/bcc/remote/shell.py b/src/python/bcc/remote/shell.py new file mode 100644 index 000000000000..e5e4d108d79f --- /dev/null +++ b/src/python/bcc/remote/shell.py @@ -0,0 +1,70 @@ +# Copyright 2017 Joel Fernandes +# +# 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. + +import os +import pexpect as pe + +from . import remote_utils +from .base import BccRemote + +class ShellRemote(BccRemote): + def __init__(self, cmd=None): + """ + Create a local connection by spawning bpfd and communicating + with it using the spawned bpfd process's stdin and stdout. + + :param cmd: Command to execute for bpfd. If not specified, + then we default to search for bpfd in the path. + :type cmd: str + """ + if cmd == None: + cmd = 'bpfd' + + self.client = pe.spawn(cmd) + self.client.expect('STARTED_BPFD') + + def send_command(self, cmd): + remote_utils.log('Sending command {}'.format(cmd)) + + c = self.client + c.sendline(cmd) + + try: + c.expect('END_BPFD_OUTPUT') + except pe.exceptions.EOF: + return ['Command not recognized (timeout)'] + + ret = c.before.split('\n') + + # Sanitize command output + ret = [r.rstrip() for r in ret if r] + + i = 0 + while (ret[i].startswith('START_BPFD_OUTPUT') != True): + i = i + 1 + + remote_utils.log('Received {}'.format(ret[(i+1):][:50])) + return ret[(i+1):] + + def send_exit_command(self): + remote_utils.log('Sending command exit') + + self.client.sendline('exit') + + remote_utils.log('Success: bpfd terminated') + + def close_connection(self): + self.send_exit_command() + self.client.close(force=True) + diff --git a/src/python/bcc/table.py b/src/python/bcc/table.py index f29397e7a0b8..5e9a499aaa9e 100644 --- a/src/python/bcc/table.py +++ b/src/python/bcc/table.py @@ -18,6 +18,7 @@ import multiprocessing import os import errno +import base64 from .libbcc import lib, _RAW_CB_TYPE, _LOST_CB_TYPE from .perf import Perf @@ -144,12 +145,14 @@ def Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs): t = LruPerCpuHash(bpf, map_id, map_fd, keytype, leaftype) if t == None: raise Exception("Unknown table type %d" % ttype) + if 'libremote' in kwargs: + t.libremote = kwargs['libremote'] return t class TableBase(MutableMapping): - def __init__(self, bpf, map_id, map_fd, keytype, leaftype): + def __init__(self, bpf, map_id, map_fd, keytype, leaftype, libremote=None): self.bpf = bpf self.map_id = map_id self.map_fd = map_fd @@ -158,6 +161,7 @@ def __init__(self, bpf, map_id, map_fd, keytype, leaftype): self.ttype = lib.bpf_table_type_id(self.bpf.module, self.map_id) self.flags = lib.bpf_table_flags_id(self.bpf.module, self.map_id) self._cbs = {} + self.libremote = libremote def key_sprintf(self, key): buf = ct.create_string_buffer(ct.sizeof(self.Key) * 8) @@ -192,13 +196,43 @@ def leaf_scanf(self, leaf_str): return leaf def __getitem__(self, key): + key_p = ct.pointer(key) leaf = self.Leaf() + leaf_p = ct.pointer(leaf) + + if self.libremote: + klen = ct.sizeof(self.Key) + llen = ct.sizeof(self.Leaf) + kstr = base64.b64encode(ct.string_at(ct.cast(key_p, ct.c_void_p), klen)) + + (ret, lstr) = self.libremote.bpf_lookup_elem(self.map_fd, kstr, klen, llen) + if ret < 0: + raise KeyError("bpf lookup failed, returned {}".format(lstr)) + lstr = lstr[0] + + lbin_p = ct.c_char_p(base64.b64decode(lstr)) + ct.memmove(leaf_p, lbin_p, llen) + return leaf + res = lib.bpf_lookup_elem(self.map_fd, ct.byref(key), ct.byref(leaf)) if res < 0: raise KeyError return leaf def __setitem__(self, key, leaf): + key_p = ct.pointer(key) + leaf_p = ct.pointer(leaf) + + if self.libremote: + klen = ct.sizeof(key) + llen = ct.sizeof(leaf) + kstr = base64.b64encode(ct.string_at(ct.cast(key_p, ct.c_void_p), klen)) + lstr = base64.b64encode(ct.string_at(ct.cast(leaf_p, ct.c_void_p), llen)) + + if self.libremote.bpf_update_elem(self.map_fd, kstr, klen, lstr, llen, 0) < 0: + raise Exception("Could not update table") + return + res = lib.bpf_update_elem(self.map_fd, ct.byref(key), ct.byref(leaf), 0) if res < 0: @@ -206,7 +240,15 @@ def __setitem__(self, key, leaf): raise Exception("Could not update table: %s" % errstr) def __delitem__(self, key): - res = lib.bpf_delete_elem(self.map_fd, ct.byref(key)) + key_p = ct.pointer(key) + + if self.libremote: + klen = ct.sizeof(self.Key) + kstr = base64.b64encode(ct.string_at(ct.cast(key_p, ct.c_void_p), klen)) + res = self.libremote.bpf_delete_elem(self.map_fd, kstr, klen) + else: + res = lib.bpf_delete_elem(self.map_fd, ct.byref(key)) + if res < 0: raise KeyError @@ -235,6 +277,12 @@ def values(self): return [value for value in self.itervalues()] def clear(self): + # Clear map optimized for remotes + if self.libremote: + klen = ct.sizeof(self.Key) + self.libremote.bpf_clear_map(self.map_fd, klen) + return + # default clear uses popitem, which can race with the bpf prog for k in self.keys(): self.__delitem__(k) @@ -267,13 +315,38 @@ def next(self): def next(self, key): next_key = self.Key() + next_key_p = ct.pointer(next_key) if key is None: - res = lib.bpf_get_first_key(self.map_fd, ct.byref(next_key), - ct.sizeof(self.Key)) + if self.libremote: + size = ct.sizeof(self.Key) + vlen = ct.sizeof(self.Leaf) + ret = self.libremote.bpf_get_first_key(self.map_fd, size, vlen) + if ret[0] < 0: + raise StopIteration() + key_str = ret[1][0] + key_p = ct.c_char_p(base64.b64decode(key_str)) + ct.memmove(next_key_p, key_p, size) + res = ret[0] + else: + res = lib.bpf_get_first_key(self.map_fd, ct.byref(next_key), + ct.sizeof(self.Key)) else: - res = lib.bpf_get_next_key(self.map_fd, ct.byref(key), - ct.byref(next_key)) + key_p = ct.pointer(key) + if self.libremote: + klen = ct.sizeof(self.Key) + kstr = base64.b64encode(ct.string_at(ct.cast(key_p, + ct.c_void_p), klen)) + ret = self.libremote.bpf_get_next_key(self.map_fd, kstr, klen) + if ret[0] < 0: + raise StopIteration() + ret_key_str = ret[1][0] + ret_key_p = ct.c_char_p(base64.b64decode(ret_key_str)) + ct.memmove(next_key_p, ret_key_p, klen) + res = ret[0] + else: + res = lib.bpf_get_next_key(self.map_fd, ct.byref(key), + ct.byref(next_key)) if res < 0: raise StopIteration() @@ -496,12 +569,16 @@ def __delitem__(self, key): key_id = (id(self), key) if key_id in self.bpf.perf_buffers: # The key is opened for perf ring buffer - lib.perf_reader_free(self.bpf.perf_buffers[key_id]) + if self.libremote is None: + lib.perf_reader_free(self.bpf.perf_buffers[key_id]) del self.bpf.perf_buffers[key_id] del self._cbs[key] else: # The key is opened for perf event read - lib.bpf_close_perf_event_fd(self._open_key_fds[key]) + if self.libremote: + self.libremote.bpf_close_perf_event_fd(self._open_key_fds[key]) + else: + lib.bpf_close_perf_event_fd(self._open_key_fds[key]) del self._open_key_fds[key] def open_perf_buffer(self, callback, page_cnt=8, lost_cb=None): @@ -539,12 +616,24 @@ def lost_cb_(_, lost): raise e fn = _RAW_CB_TYPE(raw_cb_) lost_fn = _LOST_CB_TYPE(lost_cb_) if lost_cb else ct.cast(None, _LOST_CB_TYPE) - reader = lib.bpf_open_perf_buffer(fn, lost_fn, None, -1, cpu, page_cnt) - if not reader: - raise Exception("Could not open perf buffer") - fd = lib.perf_reader_fd(reader) + + if self.libremote: + # fn and lost_fn are already tracked by self._cbs below so no need to pass + fd = self.libremote.bpf_open_perf_buffer(-1, cpu, page_cnt) + if fd < 0: + raise Exception("Could not open perf buffer") + else: + reader = lib.bpf_open_perf_buffer(fn, lost_fn, None, -1, cpu, page_cnt) + if not reader: + raise Exception("Could not open perf buffer") + fd = lib.perf_reader_fd(reader) + self[self.Key(cpu)] = self.Leaf(fd) - self.bpf.perf_buffers[(id(self), cpu)] = reader + if self.libremote: + self.bpf.perf_buffers[(id(self), cpu)] = fd + else: + self.bpf.perf_buffers[(id(self), cpu)] = reader + # keep a refcnt self._cbs[cpu] = (fn, lost_fn) # The actual fd is held by the perf reader, add to track opened keys diff --git a/src/python/setup.py.in b/src/python/setup.py.in index 8e05d4fe9aa7..68531505a4ea 100644 --- a/src/python/setup.py.in +++ b/src/python/setup.py.in @@ -13,5 +13,5 @@ setup(name='bcc', author='Brenden Blanco', author_email='bblanco@plumgrid.com', url='https://github.com/iovisor/bcc', - packages=['bcc'], + packages=['bcc', 'bcc.remote'], platforms=['Linux']) diff --git a/tests/cc/test_static.c b/tests/cc/test_static.c index 4af8b9308115..ee65b8d08130 100644 --- a/tests/cc/test_static.c +++ b/tests/cc/test_static.c @@ -1,6 +1,7 @@ #include "bpf_common.h" int main(int argc, char **argv) { - void *mod = bpf_module_create_c_from_string("BPF_TABLE(\"array\", int, int, stats, 10);\n", 4, NULL, 0); + void *mod = bpf_module_create_c_from_string( + "BPF_TABLE(\"array\", int, int, stats, 10);\n", 4, NULL, 0, NULL); return !(mod != NULL); } diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 116b2ef619e1..dd28989c5243 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -67,6 +67,8 @@ add_test(NAME py_test_dump_func WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_dump_func simple ${CMAKE_CURRENT_SOURCE_DIR}/test_dump_func.py) add_test(NAME py_test_tools_smoke WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_tools_smoke sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_smoke.py) +add_test(NAME py_test_tools_on_remote WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${TEST_WRAPPER} py_test_tools_on_remote sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_on_remote.py) add_test(NAME py_test_tools_memleak WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_tools_memleak sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_memleak.py) add_test(NAME py_test_usdt WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/python/test_tools_on_remote.py b/tests/python/test_tools_on_remote.py new file mode 100755 index 000000000000..861bbb92858b --- /dev/null +++ b/tests/python/test_tools_on_remote.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# Copyright (c) Jazel Canseco, 2018 +# Licensed under the Apache License, Version 2.0 (the "License") + +import os +import subprocess + +from unittest import main, skipUnless, TestCase +from test_tools_smoke import ToolTestRunner, kernel_version_ge + +class RemoteTests(TestCase, ToolTestRunner): + + def setUp(self): + self.original_env = os.environ.copy() + + os.environ["ARCH"] = "x86" + os.environ["BCC_REMOTE"] = "shell" + + def tearDown(self): + os.environ.clear() + os.environ.update(self.original_env) + + def test_biolatency(self): + self.run_with_duration("biolatency.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_biosnoop(self): + self.run_with_int("biosnoop.py") + + def test_biotop(self): + self.run_with_duration("biotop.py 1 1") + + def test_cachestat(self): + self.run_with_duration("cachestat.py 1 1") + + def test_filetop(self): + self.run_with_duration("filetop.py 1 1") + + def test_hardirqs(self): + self.run_with_duration("hardirqs.py 1 1") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_offcputime(self): + # When running this tool on a remote target, it takes much longer + # to output results (i.e. not instantaneous). This makes using + # run_with_duration() unsuitable for this test since it'll erroneously + # conclude that the tool has hanged when it is just taking some time + # to finish printing its output. + self.run_with_int("offcputime.py 1", allow_early=True) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_opensnoop(self): + self.run_with_int("opensnoop.py") + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_profile(self): + # When running this tool on a remote target, it takes much longer + # to output results (i.e. not instantaneous). This makes using + # run_with_duration() unsuitable for this test since it'll erroneously + # conclude that the tool has hanged when it is just taking some time + # to finish printing its output. + self.run_with_int("profile.py 1", allow_early=True) + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_runqlen(self): + self.run_with_duration("runqlen.py 1 1") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_stackcount(self): + self.run_with_int("stackcount.py __kmalloc -i 1") + + @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7") + def test_syscount(self): + self.run_with_int("syscount.py -i 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_trace(self): + self.run_with_int("trace.py SyS_open") + +if __name__ == "__main__": + main() diff --git a/tests/python/test_tools_smoke.py b/tests/python/test_tools_smoke.py index 96b1197a6cdb..024cd2b2c813 100755 --- a/tests/python/test_tools_smoke.py +++ b/tests/python/test_tools_smoke.py @@ -21,8 +21,7 @@ def kernel_version_ge(major, minor): return False return True -@skipUnless(kernel_version_ge(4,1), "requires kernel >= 4.1") -class SmokeTests(TestCase): +class ToolTestRunner(object): # Use this for commands that have a built-in timeout, so they only need # to be killed in case of a hard hang. def run_with_duration(self, command, timeout=10): @@ -51,6 +50,8 @@ def run_with_int(self, command, timeout=5, kill_timeout=5, self.assertTrue((rc == 0 and allow_early) or rc == 124 or (rc == 137 and kill), "rc was %d" % rc) +@skipUnless(kernel_version_ge(4,1), "requires kernel >= 4.1") +class SmokeTests(TestCase, ToolTestRunner): def kmod_loaded(self, mod): with open("/proc/modules", "r") as mods: reg = re.compile("^%s\s" % mod) diff --git a/tools/syscount.py b/tools/syscount.py index e219e91d6ad7..d02ad51714fc 100755 --- a/tools/syscount.py +++ b/tools/syscount.py @@ -506,15 +506,9 @@ def print_stats(): agg_colname = "PID COMM" if args.process else "SYSCALL" time_colname = "TIME (ms)" if args.milliseconds else "TIME (us)" -def comm_for_pid(pid): - try: - return open("/proc/%d/comm" % pid, "rb").read().strip() - except Exception: - return b"[unknown]" - def agg_colval(key): if args.process: - return b"%-6d %-15s" % (key.value, comm_for_pid(key.value)) + return b"%-6d %-15s" % (key.value, bpf.comm_for_pid(key.value)) else: return syscalls.get(key.value, b"[unknown: %d]" % key.value)