From c62b2b847d826823e23d4fde3292a386413ec442 Mon Sep 17 00:00:00 2001 From: Jazel Canseco Date: Thu, 15 Mar 2018 10:54:29 -0700 Subject: [PATCH 1/7] bpfd: Add BPFd (BPF daemon) This patch adds BPFd, a standalone executable designed to provide BCC the ability to work across system and architecture boundaries. This is done by loading the BPFd executable onto a remote target device to have it act as a proxy for whenever a BCC tool wishes to perform an operation on the system (e.g. load BPF programs, read /proc/kallsyms, attach kprobes, etc.). This arrangement allows developers to have kernel sources and the LLVM stack on a separate host machine (e.g. the development machine) instead of needing to set all these up on the target device, thereby allowing for the drastic reduction of space required on a target for BCC tools to run. The reduction of the space requirement, in particular, becomes a much more critical factor for devices that have more limited disk space (e.g. embedded devices). In addition, the above set-up also allows developers to run clang on a different architecture than the target's architecture, thus facilitating cross-compilation development. However, the natural disadvantage for cross-developers is that there is a need to have a copy of the target's kernel sources on the host for the above set-up to work. For more information, please check out the README in the original BPFd repository (https://github.com/joelagnel/bpfd) and this LWN article explaining the purpose of and how BPFd works in more detail (https://lwn.net/Articles/744522/) Signed-off-by: Jazel Canseco Signed-off-by: Adrian Ratiu --- src/cc/CMakeLists.txt | 1 + src/cc/bpfd/CMakeLists.txt | 8 + src/cc/bpfd/base64.c | 268 ++++++++++ src/cc/bpfd/base64.h | 4 + src/cc/bpfd/bpfd.c | 813 +++++++++++++++++++++++++++++++ src/cc/bpfd/bpfd.h | 68 +++ src/cc/bpfd/cmd_parsers.c | 137 ++++++ src/cc/bpfd/cmd_parsers.h | 52 ++ src/cc/bpfd/remote_perf_reader.c | 98 ++++ src/cc/bpfd/utils.c | 98 ++++ src/cc/bpfd/utils.h | 23 + src/cc/perf_reader.c | 14 - src/cc/perf_reader.h | 14 +- 13 files changed, 1583 insertions(+), 15 deletions(-) create mode 100644 src/cc/bpfd/CMakeLists.txt create mode 100644 src/cc/bpfd/base64.c create mode 100644 src/cc/bpfd/base64.h create mode 100644 src/cc/bpfd/bpfd.c create mode 100644 src/cc/bpfd/bpfd.h create mode 100644 src/cc/bpfd/cmd_parsers.c create mode 100644 src/cc/bpfd/cmd_parsers.h create mode 100644 src/cc/bpfd/remote_perf_reader.c create mode 100644 src/cc/bpfd/utils.c create mode 100644 src/cc/bpfd/utils.h diff --git a/src/cc/CMakeLists.txt b/src/cc/CMakeLists.txt index bd34fd481785..66cd08c0c6bd 100644 --- a/src/cc/CMakeLists.txt +++ b/src/cc/CMakeLists.txt @@ -102,6 +102,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/bpfd/CMakeLists.txt b/src/cc/bpfd/CMakeLists.txt new file mode 100644 index 000000000000..c570ea3b2f75 --- /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-static) +target_link_libraries(bpfd bcc-static) + +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..849ae2c71ecf --- /dev/null +++ b/src/cc/bpfd/base64.c @@ -0,0 +1,268 @@ +/* + * 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); + + fclose(fp); + 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..0e4c003f60d2 --- /dev/null +++ b/src/cc/bpfd/bpfd.c @@ -0,0 +1,813 @@ +/* + * 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" +#include "libbpf/src/bpf.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; + + ret = bcc_prog_load((enum bpf_prog_type)type, name, insns, prog_len, + (const char *)license, kern_version, 0, NULL, 0); + + printf("bcc_prog_load: ret=%d\n", ret); + free(bin_buf); + 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); + dump_buf = NULL; + } + +get_done: + if (kbin) + free(kbin); + if (lbin) + free(lbin); + if (kstr) + free(kstr); + if (lstr) + free(lstr); + if (next_kbin) + free(next_kbin); + if (!rets && dump_buf) + free(dump_buf); + 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, same_keys = 0; + + 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); + if (!memcmp(kbin, next_kbin, klen)) + same_keys = 1; + + tmp = kbin; + kbin = next_kbin; + next_kbin = NULL; + free(tmp); + } while (ret >= 0 && !same_keys); + + 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"); + fflush(stdout); + + while (fgets(line_buf, LINEBUF_SIZE, stdin)) { + line_buf[strcspn(line_buf, "\n")] = '\0'; + arg_index = 0; + + if (!strlen(line_buf)) + continue; + + in = parse_user_input(line_buf); + if (in == NULL) + continue; + + if (!strncmp(in->cmd, "exit", 5)) { + 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, maxactive; + char *ev_name, *fn_name; + uint64_t offset; + + PARSE_INT(prog_fd); + PARSE_INT(type); + PARSE_STR(ev_name); + PARSE_STR(fn_name); + PARSE_UINT64(offset); + PARSE_INT(maxactive); + + ret = bpf_attach_kprobe(prog_fd, type, ev_name, fn_name, offset, maxactive); + 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")) { + struct bpf_create_map_attr attr = {}; + int ret, map_type, allow_rlimit; + char *name; + + PARSE_INT(map_type); + PARSE_STR(name); + PARSE_UINT32(attr.key_size); + PARSE_UINT32(attr.value_size); + PARSE_UINT32(attr.max_entries); + PARSE_UINT32(attr.map_flags); + PARSE_INT(allow_rlimit); + attr.map_type = map_type; + attr.name = name; + + ret = bcc_create_map_xattr(&attr, allow_rlimit); + printf("bcc_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; + + PARSE_INT(timeout); + PARSE_INT(len); + + fds = malloc(len * sizeof(*fds)); + if (!fds) + printf("perf_reader_poll: ret=%d\n", -ENOMEM); + + for (i = 0; i < len; i++) { + PARSE_INT(fds[i]); + } + + remote_perf_reader_poll(fds, len, timeout); + free(fds); + + } 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..b861b39e665e --- /dev/null +++ b/src/cc/bpfd/cmd_parsers.c @@ -0,0 +1,137 @@ +/* + * 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 i, num_tokens; + + num_tokens = count_num_tokens(str); + if (num_tokens == 0) + return NULL; + + in = (struct user_input *) calloc(1, sizeof(struct user_input)); + + if (num_tokens > 1) { + in->num_args = num_tokens - 1; + in->args = (char **) calloc(in->num_args, sizeof(char *)); + } + + 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); + + for (i = 0; i < in->num_args; i++) { + token = strtok(NULL, " "); + if (!token) + continue; + in->args[i] = (char *)malloc(strlen(token) + 1); + strcpy(in->args[i], token); + } + + 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/perf_reader.c b/src/cc/perf_reader.c index 3cab0153224e..872e48f6036b 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, From 7a67d16a661f7859ec408aa6292f1341ab4da763 Mon Sep 17 00:00:00 2001 From: Jazel Canseco Date: Mon, 16 Apr 2018 14:00:02 -0700 Subject: [PATCH 2/7] Add BPFd and the BPF shared library to the rpm/debian build steps The bpf shared library (i.e. libbpf.so) is included since the bpfd executable dynamically links to it. Signed-off-by: Jazel Canseco Signed-off-by: Adrian Ratiu --- SPECS/bcc+clang.spec | 9 +++++++++ SPECS/bcc.spec | 9 +++++++++ debian/bpfd.install | 1 + debian/control | 5 +++++ debian/libbcc.install | 1 + 5 files changed, 25 insertions(+) create mode 100644 debian/bpfd.install 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 f74bb6149760..c1c3a74b612c 100644 --- a/SPECS/bcc.spec +++ b/SPECS/bcc.spec @@ -138,6 +138,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/* @@ -169,6 +175,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 4b5a22e75897..d3a1421fa5fc 100644 --- a/debian/control +++ b/debian/control @@ -55,3 +55,8 @@ Provides: bpfcc-lua Conflicts: bpfcc-lua Depends: libbcc (= ${binary:Version}) 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 From 10fe937984dffed207f0445e7e7e028ab8ca22a7 Mon Sep 17 00:00:00 2001 From: Jazel Canseco Date: Sun, 7 Jan 2018 11:59:09 -0800 Subject: [PATCH 3/7] bpfd, bcc/remote: Create bcc remotes to hook up BCC to BPFd This modifies BCC to query BPFd whenever BCC wishes to perform an operation on a remote target device (e.g. load BPF programs, read /proc/kallsyms, attach kprobes, etc.) If no remote target device has been configured, BCC defaults to performing the operations on the local system just like before. Signed-off-by: Jazel Canseco Signed-off-by: Adrian Ratiu --- src/cc/bcc_common.cc | 16 +- src/cc/bcc_common.h | 7 +- src/cc/bpf_module.cc | 6 +- src/python/bcc/__init__.py | 291 ++++++++++++++++------ src/python/bcc/libbcc.py | 8 +- src/python/bcc/remote/__init__.py | 13 + src/python/bcc/remote/adb.py | 44 ++++ src/python/bcc/remote/base.py | 55 ++++ src/python/bcc/remote/libremote.py | 344 ++++++++++++++++++++++++++ src/python/bcc/remote/remote_utils.py | 21 ++ src/python/bcc/remote/shell.py | 89 +++++++ src/python/bcc/table.py | 116 ++++++++- tests/cc/test_static.c | 2 +- tests/python/CMakeLists.txt | 2 + tests/python/test_tools_on_remote.py | 77 ++++++ tests/python/test_tools_smoke.py | 5 +- tools/syscount.py | 8 +- 17 files changed, 1004 insertions(+), 100 deletions(-) create mode 100644 src/python/bcc/remote/__init__.py create mode 100644 src/python/bcc/remote/adb.py create mode 100644 src/python/bcc/remote/base.py create mode 100644 src/python/bcc/remote/libremote.py create mode 100644 src/python/bcc/remote/remote_utils.py create mode 100644 src/python/bcc/remote/shell.py create mode 100755 tests/python/test_tools_on_remote.py diff --git a/src/cc/bcc_common.cc b/src/cc/bcc_common.cc index 182281119e87..ed73455138e6 100644 --- a/src/cc/bcc_common.cc +++ b/src/cc/bcc_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) { @@ -27,7 +35,9 @@ void * bpf_module_create_b(const char *filename, const char *proto_filename, uns } void * bpf_module_create_c(const char *filename, unsigned flags, const char *cflags[], - int ncflags, bool allow_rlimit) { + int ncflags, bool allow_rlimit, bpf_create_map_cb_t map_cb) { + bpf_create_map_cb = map_cb; + auto mod = new ebpf::BPFModule(flags, nullptr, true, "", allow_rlimit); if (mod->load_c(filename, cflags, ncflags) != 0) { delete mod; @@ -37,7 +47,9 @@ void * bpf_module_create_c(const char *filename, unsigned flags, const char *cfl } void * bpf_module_create_c_from_string(const char *text, unsigned flags, const char *cflags[], - int ncflags, bool allow_rlimit) { + int ncflags, bool allow_rlimit, bpf_create_map_cb_t map_cb) { + bpf_create_map_cb = map_cb; + auto mod = new ebpf::BPFModule(flags, nullptr, true, "", allow_rlimit); if (mod->load_string(text, cflags, ncflags) != 0) { delete mod; diff --git a/src/cc/bcc_common.h b/src/cc/bcc_common.h index 2504ea237b8d..b16ecf95a840 100644 --- a/src/cc/bcc_common.h +++ b/src/cc/bcc_common.h @@ -25,11 +25,14 @@ extern "C" { #endif +typedef int (*bpf_create_map_cb_t)(void *data, bool allow_rlimit); +extern bpf_create_map_cb_t bpf_create_map_cb; + 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, - bool allow_rlimit); + bool allow_rlimit, bpf_create_map_cb_t map_cb); void * bpf_module_create_c_from_string(const char *text, unsigned flags, const char *cflags[], - int ncflags, bool allow_rlimit); + int ncflags, bool allow_rlimit, 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/bpf_module.cc b/src/cc/bpf_module.cc index cf6ea8f3bce2..ed759f33adf6 100644 --- a/src/cc/bpf_module.cc +++ b/src/cc/bpf_module.cc @@ -43,6 +43,7 @@ #include "exported_files.h" #include "libbpf.h" #include "bcc_btf.h" +#include "bcc_common.h" #include "libbpf/src/bpf.h" namespace ebpf { @@ -340,7 +341,10 @@ int BPFModule::load_maps(sec_map_def §ions) { attr.btf_value_type_id = map_tids[map_name].second; } - fd = bcc_create_map_xattr(&attr, allow_rlimit_); + if (bpf_create_map_cb) + fd = bpf_create_map_cb(&attr, allow_rlimit_); + else + fd = bcc_create_map_xattr(&attr, allow_rlimit_); if (fd < 0) { fprintf(stderr, "could not open bpf map: %s, error: %s\n", map_name, strerror(errno)); diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index bff5f2820e62..193b9d1e3150 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -24,12 +24,13 @@ import sys basestring = (unicode if sys.version_info[0] < 3 else str) -from .libbcc import lib, bcc_symbol, bcc_symbol_option, bcc_stacktrace_build_id, _SYM_CB_TYPE +from .libbcc import lib, bcc_symbol, bcc_symbol_option, bcc_stacktrace_build_id, _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 .version import __version__ from .disassembler import disassemble_prog, decode_map +from .remote import libremote, remote_utils _probe_limit = 1000 _num_open_probes = 0 @@ -160,6 +161,7 @@ class BPF(object): XDP_TX = 3 XDP_REDIRECT = 4 + _libremote = None _probe_repl = re.compile(b"[^a-zA-Z0-9_]") _sym_caches = {} _bsymcache = lib.bcc_buildsymcache_new() @@ -271,6 +273,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=[], allow_rlimit=True): """Create a new BPF module with the given source code. @@ -287,6 +299,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) @@ -320,7 +335,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), allow_rlimit) + self.debug, cflags_array, len(cflags_array), allow_rlimit, + _MAP_CB_TYPE(BPF._bpf_create_map_cb)) if not self.module: raise Exception("Failed to compile BPF text") else: @@ -331,7 +347,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), allow_rlimit) + cflags_array, len(cflags_array), allow_rlimit, + _MAP_CB_TYPE(BPF._bpf_create_map_cb)) if not self.module: raise Exception("Failed to compile BPF module %s" % src_file) @@ -343,6 +360,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) @@ -367,21 +392,30 @@ def load_func(self, func_name, prog_type): log_level = 2 elif (self.debug & DEBUG_BPF): log_level = 1 - fd = lib.bcc_func_load(self.module, 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.bcc_func_load(self.module, 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 @@ -488,7 +522,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, name, reducer=reducer) + return Table(self, map_id, map_fd, keytype, leaftype, name, reducer=reducer, + libremote=BPF._libremote) def __getitem__(self, key): if key not in self.tables: @@ -507,6 +542,13 @@ def __delitem__(self, key): def __iter__(self): return self.tables.__iter__() + @staticmethod + def _bpf_create_map_cb(data, allow_rlimit): + if BPF._libremote: + return BPF._libremote.bpf_create_map(data, allow_rlimit) + else: + return lib.bcc_create_map_xattr(data, allow_rlimit) + @staticmethod def attach_raw_socket(fn, dev): dev = _assert_is_bytes(dev) @@ -525,52 +567,65 @@ 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]) + + 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(avail_file): + fns = [] in_init_section = 0 in_irq_section = 0 - with open("/proc/kallsyms", "rb") as avail_file: - for line in avail_file: - (t, fn) = line.rstrip().split()[1:3] - # Skip all functions defined between __init_begin and - # __init_end - if in_init_section == 0: - if fn == b'__init_begin': - in_init_section = 1 - continue - elif in_init_section == 1: - if fn == b'__init_end': - in_init_section = 2 - continue - # Skip all functions defined between __irqentry_text_start and - # __irqentry_text_end - if in_irq_section == 0: - if fn == b'__irqentry_text_start': - in_irq_section = 1 - continue - elif in_irq_section == 1: - if fn == b'__irqentry_text_end': - in_irq_section = 2 + for line in avail_file: + (t, fn) = line.rstrip().split()[1:3] + # Skip all functions defined between __init_begin and + # __init_end + if in_init_section == 0: + if fn == b'__init_begin': + in_init_section = 1 continue - # All functions defined as NOKPROBE_SYMBOL() start with the - # prefix _kbl_addr_*, blacklisting them by looking at the name - # allows to catch also those symbols that are defined in kernel - # modules. - if fn.startswith(b'_kbl_addr_'): - continue - # Explicitly blacklist perf-related functions, they are all - # non-attachable. - elif fn.startswith(b'__perf') or fn.startswith(b'perf_'): - continue - # Exclude all gcc 8's extra .cold functions - elif re.match(b'^.*\.cold\.\d+$', fn): + elif in_init_section == 1: + if fn == b'__init_end': + in_init_section = 2 + continue + # Skip all functions defined between __irqentry_text_start and + # __irqentry_text_end + if in_irq_section == 0: + if fn == b'__irqentry_text_start': + in_irq_section = 1 continue - if (t.lower() in [b't', b'w']) and re.match(event_re, fn) \ - and fn not in blacklist: - fns.append(fn) - return set(fns) # Some functions may appear more than once + elif in_irq_section == 1: + if fn == b'__irqentry_text_end': + in_irq_section = 2 + continue + # All functions defined as NOKPROBE_SYMBOL() start with the + # prefix _kbl_addr_*, blacklisting them by looking at the name + # allows to catch also those symbols that are defined in kernel + # modules. + if fn.startswith(b'_kbl_addr_'): + continue + # Explicitly blacklist perf-related functions, they are all + # non-attachable. + elif fn.startswith(b'__perf') or fn.startswith(b'perf_'): + continue + # Exclude all gcc 8's extra .cold functions + elif re.match(b'^.*\.cold\.\d+$', fn): + continue + 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 @@ -623,6 +678,18 @@ def fix_syscall_fnname(self, name): return self.get_syscall_fnname(name[len(prefix):]) return name + @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) + + @staticmethod + def _bpf_detach_kprobe(ev_name): + if BPF._libremote: + return BPF._libremote.bpf_detach_kprobe(ev_name) + return lib.bpf_detach_kprobe(ev_name) + def attach_kprobe(self, event=b"", event_off=0, fn_name=b"", event_re=b""): event = _assert_is_bytes(event) fn_name = _assert_is_bytes(fn_name) @@ -642,6 +709,15 @@ def attach_kprobe(self, event=b"", event_off=0, 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, + event_off, 0) + 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, event_off, 0) if fd < 0: raise Exception("Failed to attach BPF program %s to kprobe %s" % @@ -667,6 +743,15 @@ def attach_kretprobe(self, event=b"", fn_name=b"", event_re=b"", maxactive=0): 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, 0, + maxactive) + 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, 0, maxactive) if fd < 0: raise Exception("Failed to attach BPF program %s to kretprobe %s" % @@ -677,10 +762,10 @@ def attach_kretprobe(self, event=b"", fn_name=b"", event_re=b"", maxactive=0): def detach_kprobe_event(self, ev_name): if ev_name not in self.kprobe_fds: raise Exception("Kprobe %s is not attached" % ev_name) - 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) + res = BPF._bpf_detach_kprobe(ev_name) if res < 0: raise Exception("Failed to detach BPF from kprobe") self._del_kprobe_fd(ev_name) @@ -760,6 +845,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) @@ -809,6 +902,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 program %s to tracepoint %s" % @@ -875,19 +976,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 @@ -915,7 +1025,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)] @@ -1007,6 +1117,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") @@ -1039,6 +1157,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") @@ -1048,10 +1174,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) @@ -1234,8 +1365,10 @@ def sym(addr, pid, show_module=False, show_offset=False, demangle=True): else: name, offset, module = (sym.name, sym.offset, ct.cast(sym.module, ct.c_char_p).value) + elif BPF._libremote: + name, offset, module = BPF._libremote.sym(pid, addr, demangle) else: - name, offset, module = BPF._sym_cache(pid).resolve(addr, demangle) + 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]" @@ -1264,7 +1397,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() @@ -1294,6 +1430,21 @@ def perf_buffer_poll(self, timeout = -1): Poll from all open perf ring buffers, calling the callback that was provided when calling open_perf_buffer for each entry. """ + if BPF._libremote: + fd_callbacks = [] + for k, v in iter(self.perf_buffers.items()): + # 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 diff --git a/src/python/bcc/libbcc.py b/src/python/bcc/libbcc.py index e98bb1401ed0..afd5c6e51e8a 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 bcc_common.h +_MAP_CB_TYPE = ct.CFUNCTYPE(ct.c_int, ct.c_void_p, ct.c_bool) 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.c_bool] + ct.POINTER(ct.c_char_p), ct.c_int, ct.c_bool, _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.c_bool] + ct.POINTER(ct.c_char_p), ct.c_int, ct.c_bool, _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 @@ -282,3 +283,6 @@ class bcc_usdt_argument(ct.Structure): lib.bcc_usdt_foreach_uprobe.restype = None lib.bcc_usdt_foreach_uprobe.argtypes = [ct.c_void_p, _USDT_PROBE_CB] + +lib.bcc_create_map_xattr.restype = ct.c_int +lib.bcc_create_map_xattr.argtypes = [ct.c_void_p, ct.c_bool] 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..d6536fe77218 --- /dev/null +++ b/src/python/bcc/remote/libremote.py @@ -0,0 +1,344 @@ +# 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 +import os + +from .shell import ShellRemote +from .adb import AdbRemote + +class BpfCreateMapAttr(ct.Structure): + _fields_ = [("name", ct.c_char_p), + ("map_type", ct.c_uint), + ("map_flags", ct.c_uint), + ("key_size", ct.c_uint), + ("value_size", ct.c_uint), + ("max_entries", ct.c_uint)] + +class LibRemote(object): + def __init__(self, remote_name, remote_arg=None): + # Get the Remote class object + cls = globals()[remote_name.capitalize() + 'Remote'] + + # 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(b'Command not recognized'): + print('Command not recognized! cmd: {}'.format(cmd)) + return (-1, []) + + if len(ret) > 1 and ret[1].startswith(b'free(): '): + print('BPFd crashed ({}) while processing cmd: {}'.format(ret[1], cmd)) + print('To debug, run with BCC_REMOTE_DEBUG=1 env var and look at the BPFd core dumps') + os._exit(1) # exit immediately, we can't do cleanups as bpfd is dead + + if ret[0].startswith(b'Open failed, ignoring'): + return (-1, []) + + # Assume success if first list element doesn't have ret= + if not b'ret=' in ret[0]: + return (0, ret) + + m = re.search(b"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, offset, maxactive=0): + evname = evname.decode("utf-8") + fnname = fnname.decode("utf-8") + cmd = "BPF_ATTACH_KPROBE {} {} {} {} {} {}".format( + fd, t, evname, fnname, offset, maxactive) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_detach_kprobe(self, evname): + evname = evname.decode("utf-8") + 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): + evname = evname.decode("utf-8") + binpath = binpath.decode("utf-8") + 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): + evname = evname.decode("utf-8") + 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): + name = name.decode("utf-8") + license_str = license_str.decode("utf-8") + func_len = len(func_str) + func_str = base64.b64encode(func_str).decode("utf-8") + cmd = "BPF_PROG_LOAD {} {} {} {} {} {}".format( + prog_type, name, func_len, license_str, kern_version, func_str) + ret = self._remote_send_command(cmd) + return ret[0] + + def bpf_create_map(self, data, allow_rlimit): + attr = ct.cast(data, ct.POINTER(BpfCreateMapAttr)).contents + name = attr.name.decode("utf-8") + cmd = "BPF_CREATE_MAP {} {} {} {} {} {} {}".format( + attr.map_type, name, attr.key_size, attr.value_size, attr.max_entries, + attr.map_flags, int(allow_rlimit == True)) + 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): + kstr = kstr.decode("utf-8") + lstr = lstr.decode("utf-8") + 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.decode("utf-8"), 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): + kstr = kstr.decode("utf-8") + 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): + kstr = kstr.decode("utf-8") + 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(b" ") + 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(b";") + 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(b";") + 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..de863ada0934 --- /dev/null +++ b/src/python/bcc/remote/shell.py @@ -0,0 +1,89 @@ +# 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 sys +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, echo=False, timeout=None) + self.client.expect('STARTED_BPFD') + + def send_command(self, cmd): + remote_utils.log('Sending command {}'.format(cmd)) + + c = self.client + raise_interrupt = False + + try: + c.sendline(cmd) + except KeyboardInterrupt: + # raise a SystemExit exception so the interpreter can call the proper + # BPF module atexit.register(self.cleanup) and not spew a traceback + # Important: do not kill the pexpect child here so the cleanup can be done + sys.exit(0) + + while c.isalive(): + try: + c.expect('END_BPFD_OUTPUT') + break + except pe.exceptions.EOF: + return [b'Command not recognized (timeout)'] + except KeyboardInterrupt: + # stop any blocking perf reader polls before triggering exit cleanups + c.sendline() + raise_interrupt = True + + if raise_interrupt: + sys.exit(0) + + ret = c.before.split(b'\n') + + # Sanitize command output + ret = [r.rstrip() for r in ret if r] + + i = 0 + while (ret[i].startswith(b'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 d32400e914cf..05b94acbbcb2 100644 --- a/src/python/bcc/table.py +++ b/src/python/bcc/table.py @@ -19,6 +19,7 @@ import os import errno import re +import base64 from .libbcc import lib, _RAW_CB_TYPE, _LOST_CB_TYPE from .perf import Perf @@ -184,12 +185,14 @@ def Table(bpf, map_id, map_fd, keytype, leaftype, name, **kwargs): t = CpuMap(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, name=None): + def __init__(self, bpf, map_id, map_fd, keytype, leaftype, name=None, libremote=None): self.bpf = bpf self.map_id = map_id self.map_fd = map_fd @@ -199,6 +202,7 @@ def __init__(self, bpf, map_id, map_fd, keytype, leaftype, name=None): self.flags = lib.bpf_table_flags_id(self.bpf.module, self.map_id) self._cbs = {} self._name = name + self.libremote = libremote def key_sprintf(self, key): buf = ct.create_string_buffer(ct.sizeof(self.Key) * 8) @@ -233,20 +237,59 @@ 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: + errstr = os.strerror(ct.get_errno()) + raise Exception("Could not update remote table: %s" % errstr) + return + res = lib.bpf_update_elem(self.map_fd, ct.byref(key), ct.byref(leaf), 0) if res < 0: errstr = os.strerror(ct.get_errno()) 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 @@ -275,6 +318,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) @@ -307,13 +356,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() @@ -580,12 +654,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 _get_event_class(self): @@ -689,12 +767,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/tests/cc/test_static.c b/tests/cc/test_static.c index 919b0742076b..bc6ecfeefebd 100644 --- a/tests/cc/test_static.c +++ b/tests/cc/test_static.c @@ -1,6 +1,6 @@ #include "bcc_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, true); + void *mod = bpf_module_create_c_from_string("BPF_TABLE(\"array\", int, int, stats, 10);\n", 4, NULL, 0, true, NULL); return !(mod != NULL); } diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index f323ac1b334b..3eb56f0e8e65 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -71,6 +71,8 @@ add_test(NAME py_test_disassembler WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_disassembler sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_disassembler.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..e164f6407324 --- /dev/null +++ b/tests/python/test_tools_on_remote.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# Copyright (c) Jazel Canseco, 2018 +# Licensed under the Apache License, Version 2.0 (the "License") +# +# Some of these tool tests run significantly slower on remote targets than locally +# (also python 2 is much slower than python 3) so timeouts had to be increased. + +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", timeout=20) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_biosnoop(self): + self.run_with_int("biosnoop.py", kill_timeout=20) + + def test_biotop(self): + self.run_with_int("biotop.py 1 1", kill_timeout=20) + + def test_cachestat(self): + self.run_with_duration("cachestat.py 1 1") + + def test_filetop(self): + self.run_with_duration("filetop.py 1 1", timeout=30) + + def test_fileslower(self): + self.run_with_int("fileslower.py") + + 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): + self.run_with_duration("offcputime.py -Kk 1", timeout=60) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_opensnoop(self): + self.run_with_int("opensnoop.py", kill_timeout=20) + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_profile(self): + self.run_with_duration("profile.py 1", timeout=20) + + @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_duration("stackcount.py __kmalloc -i 1 -D 1", timeout=50) + + @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7") + def test_syscount(self): + self.run_with_duration("syscount.py -i 1 -d 5", timeout=20) + + @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 e1b10ae2810c..1223ca2b12c3 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 6cbea11620c8..7a9110e84918 100755 --- a/tools/syscount.py +++ b/tools/syscount.py @@ -169,15 +169,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 syscall_name(key.value) From 5ba2bee810dde0e96bfb386dd5830927240a0c81 Mon Sep 17 00:00:00 2001 From: Jazel Canseco Date: Fri, 12 Jan 2018 17:44:09 -0800 Subject: [PATCH 4/7] bcc/remote: setup.py: Add remote to setup for packaging Signed-off-by: Jazel Canseco --- src/python/setup.py.in | 2 +- tests/python/test_tools_on_remote.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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/python/test_tools_on_remote.py b/tests/python/test_tools_on_remote.py index e164f6407324..9b1e7786f1f6 100755 --- a/tests/python/test_tools_on_remote.py +++ b/tests/python/test_tools_on_remote.py @@ -51,11 +51,11 @@ def test_offcputime(self): @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_opensnoop(self): - self.run_with_int("opensnoop.py", kill_timeout=20) + self.run_with_int("opensnoop.py", kill_timeout=60) @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") def test_profile(self): - self.run_with_duration("profile.py 1", timeout=20) + self.run_with_duration("profile.py 1", timeout=60) @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") def test_runqlen(self): @@ -63,11 +63,11 @@ def test_runqlen(self): @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") def test_stackcount(self): - self.run_with_duration("stackcount.py __kmalloc -i 1 -D 1", timeout=50) + self.run_with_duration("stackcount.py __kmalloc -i 1 -D 1", timeout=60) @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7") def test_syscount(self): - self.run_with_duration("syscount.py -i 1 -d 5", timeout=20) + self.run_with_duration("syscount.py -i 1 -d 5", timeout=60) @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_trace(self): From 72015269a45b2386c64eba2bb1297fc90ad638bd Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Sat, 23 Mar 2019 22:51:51 +0200 Subject: [PATCH 5/7] bpfd: link only a subset of bcc libbcc is big because it links to llvm/clang and, linking directly to it, bpfd has a much bigger footprint than necessary. Since we don't want to compile restricted-C to eBPF on embedded devices running just bpfd, we can achieve a much smaller disk footprint by linking only the minimum objects required by bpfd to run and communicate with the host bcc to load/unload the pre-compiled ebpf programs. Before this commit: $ ldd src/cc/bpfd/bpfd linux-vdso.so.1 (0x00007ffecc3df000) libz.so.1 => /usr/lib/libz.so.1 (0x00007f02b5606000) libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f02b5601000) libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007f02b5592000) libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f02b5571000) libelf.so.1 => /usr/lib/libelf.so.1 (0x00007f02b5557000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f02b53c8000) libm.so.6 => /usr/lib/libm.so.6 (0x00007f02b5241000) libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f02b5227000) libc.so.6 => /usr/lib/libc.so.6 (0x00007f02b5063000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f02b7a67000) $ ls -lsha src/cc/bpfd/bpfd 40M -rwxr-xr-x 1 adi adi 40M Mar 25 10:59 src/cc/bpfd/bpfd After: $ ldd ./src/cc/bpfd/bpfd linux-vdso.so.1 (0x00007ffffc5f1000) libelf.so.1 => /usr/lib/libelf.so.1 (0x00007f93b60d6000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f93b5f47000) libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f93b5f2d000) libc.so.6 => /usr/lib/libc.so.6 (0x00007f93b5d69000) libz.so.1 => /usr/lib/libz.so.1 (0x00007f93b5b52000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f93b614f000) libm.so.6 => /usr/lib/libm.so.6 (0x00007f93b59cd000) $ ls -lsha ./src/cc/bpfd/bpfd 204K -rwxr-xr-x 1 adi adi 203K Mar 23 19:18 ./src/cc/bpfd/bpfd Signed-off-by: Adrian Ratiu --- src/cc/bpfd/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cc/bpfd/CMakeLists.txt b/src/cc/bpfd/CMakeLists.txt index c570ea3b2f75..8b1e67b00cdd 100644 --- a/src/cc/bpfd/CMakeLists.txt +++ b/src/cc/bpfd/CMakeLists.txt @@ -1,8 +1,12 @@ # Copyright (c) Jazel Canseco, 2018 # Licensed under the Apache License, Version 2.0 (the "License") +add_library(bcc-bpfd-static STATIC ../common.cc ../bcc_syms.cc ../ns_guard.cc ../bcc_proc.c + ../libbpf.c ../perf_reader.c ../bcc_elf.c ../bcc_perf_map.c) + add_executable(bpfd bpfd.c base64.c remote_perf_reader.c utils.c cmd_parsers.c) target_link_libraries(bpfd bpf-static) -target_link_libraries(bpfd bcc-static) +target_link_libraries(bpfd bcc-bpfd-static) +target_link_libraries(bpfd ${LIBELF_LIBRARIES}) install(TARGETS bpfd RUNTIME DESTINATION bin) From 0e78223a999c8e01435ea83a59b1e16c4122f772 Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Sat, 30 Mar 2019 16:35:53 +0200 Subject: [PATCH 6/7] bpfd/CMakeLists: allow standalone builds Building bpfd should not require also building full BCC, because bpfd is much smaller (~180kb release binary), has fewer dependencies and is easier to cross compile and run on resource constrained embedded devices without LLVM/Python. This enables standalone bpfd builds, for example, to cross-compile bpfd for 32bit arm systems: $ mkdir build; cd build $ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-arm.cmake .. $ make When running the above cross-build instructions, the proper cross-compilation dependencies (libelf, libz, libstdc++) and toolchains are needed (can be added to CMAKE_FIND_ROOT_PATH). Bpfd is also built when compiling full BCC. Distribution packagers can package bpfd stand-alone like the BCC python bindings or together with the rest of bcc. Signed-off-by: Adrian Ratiu --- SPECS/bcc+clang.spec | 1 - SPECS/bcc.spec | 1 - debian/control | 1 - debian/libbcc.install | 1 - src/cc/bpfd/CMakeLists.txt | 24 ++++++++++++++++++++--- src/cc/bpfd/cmake/toolchain-aarch64.cmake | 20 +++++++++++++++++++ src/cc/bpfd/cmake/toolchain-arm.cmake | 21 ++++++++++++++++++++ 7 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/cc/bpfd/cmake/toolchain-aarch64.cmake create mode 100644 src/cc/bpfd/cmake/toolchain-arm.cmake diff --git a/SPECS/bcc+clang.spec b/SPECS/bcc+clang.spec index be7595f8e576..bb5664e98a52 100644 --- a/SPECS/bcc+clang.spec +++ b/SPECS/bcc+clang.spec @@ -81,7 +81,6 @@ 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 diff --git a/SPECS/bcc.spec b/SPECS/bcc.spec index c1c3a74b612c..7b333120dab6 100644 --- a/SPECS/bcc.spec +++ b/SPECS/bcc.spec @@ -140,7 +140,6 @@ 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 diff --git a/debian/control b/debian/control index d3a1421fa5fc..66848b109b59 100644 --- a/debian/control +++ b/debian/control @@ -58,5 +58,4 @@ 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 7829b318552b..0cb867e236c0 100644 --- a/debian/libbcc.install +++ b/debian/libbcc.install @@ -1,4 +1,3 @@ usr/include/bcc/* usr/lib/*/libbcc* -usr/lib/*/libbpf* usr/lib/*/pkgconfig/libbcc.pc diff --git a/src/cc/bpfd/CMakeLists.txt b/src/cc/bpfd/CMakeLists.txt index 8b1e67b00cdd..7d583ed194df 100644 --- a/src/cc/bpfd/CMakeLists.txt +++ b/src/cc/bpfd/CMakeLists.txt @@ -1,12 +1,30 @@ # Copyright (c) Jazel Canseco, 2018 +# Copyright (c) Adrian Ratiu, 2019 # Licensed under the Apache License, Version 2.0 (the "License") +cmake_minimum_required(VERSION 2.8.7) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake) + +find_package(LibElf REQUIRED) +find_package(ZLIB REQUIRED) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../libbpf/include + ${CMAKE_CURRENT_SOURCE_DIR}/../libbpf/include/uapi) + add_library(bcc-bpfd-static STATIC ../common.cc ../bcc_syms.cc ../ns_guard.cc ../bcc_proc.c ../libbpf.c ../perf_reader.c ../bcc_elf.c ../bcc_perf_map.c) +file(GLOB libbpf_sources "../libbpf/src/*.c") +add_library(bpf-bpfd-static STATIC ../libbpf.c ${libbpf_sources}) + add_executable(bpfd bpfd.c base64.c remote_perf_reader.c utils.c cmd_parsers.c) -target_link_libraries(bpfd bpf-static) -target_link_libraries(bpfd bcc-bpfd-static) -target_link_libraries(bpfd ${LIBELF_LIBRARIES}) +target_link_libraries(bpfd bpf-bpfd-static bcc-bpfd-static) +target_link_libraries(bpfd ${LIBELF_LIBRARIES} ${ZLIB_LIBRARIES}) install(TARGETS bpfd RUNTIME DESTINATION bin) diff --git a/src/cc/bpfd/cmake/toolchain-aarch64.cmake b/src/cc/bpfd/cmake/toolchain-aarch64.cmake new file mode 100644 index 000000000000..cf2c110c2330 --- /dev/null +++ b/src/cc/bpfd/cmake/toolchain-aarch64.cmake @@ -0,0 +1,20 @@ +# Copyright (c) Jazel Canseco, 2018 +# Licensed under the Apache License, Version 2.0 (the "License") +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +if(NOT CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +endif() + +if(NOT CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) +endif() + +if(NOT CMAKE_FIND_ROOT_PATH) + set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/src/cc/bpfd/cmake/toolchain-arm.cmake b/src/cc/bpfd/cmake/toolchain-arm.cmake new file mode 100644 index 000000000000..217dee173867 --- /dev/null +++ b/src/cc/bpfd/cmake/toolchain-arm.cmake @@ -0,0 +1,21 @@ +# Copyright (c) Jazel Canseco, 2018 +# Copyright (c) Adrian Ratiu, 2019 +# Licensed under the Apache License, Version 2.0 (the "License") +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +if(NOT CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) +endif() + +if(NOT CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) +endif() + +if(NOT CMAKE_FIND_ROOT_PATH) + set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf) +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) From 90bf55a77f14c4a6e1a710ab4549d97742d17056 Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Tue, 26 Mar 2019 21:35:54 +0200 Subject: [PATCH 7/7] remote/ssh: add new ssh backend This backend allows BCC to connect and run eBPF on remote targets with bpfd's stdin/stdout attached to ssh sockets. It deliberately avoids the complications of ssh authentication and authorization, so the ssh communication channel should be setup before BCC runs. The environment variables necessary to use this backend: BCC_REMOTE=ssh BCC_REMOTE_SSH_USER= BCC_REMOTE_SSH_ADDR= Optional variables: BCC_REMOTE_SSH_CMD= (default runs 'bpfd' in $PATH) BCC_REMOTE_SSH_PORT= (default 22) Special priviledges (usually root/sudo access) to run BCC need to be present only on the remote machine which runs bpfd, the local host running python/bcc/llvm can run as a normal user. This backend can also be used on machines with different architectures than x86 by enabling BCC cross-compilation via the ARCH environment variable (example ARCH=arm64). Signed-off-by: Adrian Ratiu --- src/python/bcc/remote/libremote.py | 1 + src/python/bcc/remote/ssh.py | 61 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/python/bcc/remote/ssh.py diff --git a/src/python/bcc/remote/libremote.py b/src/python/bcc/remote/libremote.py index d6536fe77218..d3585798ec20 100644 --- a/src/python/bcc/remote/libremote.py +++ b/src/python/bcc/remote/libremote.py @@ -21,6 +21,7 @@ from .shell import ShellRemote from .adb import AdbRemote +from .ssh import SshRemote class BpfCreateMapAttr(ct.Structure): _fields_ = [("name", ct.c_char_p), diff --git a/src/python/bcc/remote/ssh.py b/src/python/bcc/remote/ssh.py new file mode 100644 index 000000000000..50c28777c0d4 --- /dev/null +++ b/src/python/bcc/remote/ssh.py @@ -0,0 +1,61 @@ +# Copyright 2019 Adrian Ratiu +# +# 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, time, sys +import pexpect as pe + +from .shell import ShellRemote + +class SshRemote(ShellRemote): + def __init__(self, cmd=None): + """ + Spawn bpfd on a remote machine via ssh and communicate with its + stdin/stdout bound to the ssh socket. + + We deliberately do NOT handle ssh authentication and authorization: + make sure your ssh machines are properly set up from a security POV + with passwords securely stored in places like ssh/gpg-agents before + running this code, especially on open networks. + + Also make sure the remote user is capable of accessing the bpf syscall. + The local user running BCC doesn't need special privileges. + + :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' + + cmd_env = os.environ.get('BCC_REMOTE_SSH_CMD') + if cmd_env: + cmd = cmd_env + + user = os.environ.get('BCC_REMOTE_SSH_USER') + if user == None: + print('SSH backend requires BCC_REMOTE_SSH_USER environment variable definition') + sys.exit(1) + + addr = os.environ.get('BCC_REMOTE_SSH_ADDR') + if addr == None: + print('SSH backend requires BCC_REMOTE_SSH_ADDR environment variable definition') + sys.exit(1) + + port = os.environ.get('BCC_REMOTE_SSH_PORT') + if port == None: + port = "22" + + self.client = pe.spawn("ssh -p {} {}@{} {}".format(port, user, addr, cmd)) + self.client.expect('STARTED_BPFD')