// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) /* Copyright (C) 2017-2018 Netronome Systems, Inc. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cfg.h" #include "main.h" #include "xlated_dumper.h" enum dump_mode { DUMP_JITED, DUMP_XLATED, }; static const char * const attach_type_strings[] = { [BPF_SK_SKB_STREAM_PARSER] = "stream_parser", [BPF_SK_SKB_STREAM_VERDICT] = "stream_verdict", [BPF_SK_MSG_VERDICT] = "msg_verdict", [BPF_FLOW_DISSECTOR] = "flow_dissector", [__MAX_BPF_ATTACH_TYPE] = NULL, }; static enum bpf_attach_type parse_attach_type(const char *str) { enum bpf_attach_type type; for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) { if (attach_type_strings[type] && is_prefix(str, attach_type_strings[type])) return type; } return __MAX_BPF_ATTACH_TYPE; } static void print_boot_time(__u64 nsecs, char *buf, unsigned int size) { struct timespec real_time_ts, boot_time_ts; time_t wallclock_secs; struct tm load_tm; buf[--size] = '\0'; if (clock_gettime(CLOCK_REALTIME, &real_time_ts) || clock_gettime(CLOCK_BOOTTIME, &boot_time_ts)) { perror("Can't read clocks"); snprintf(buf, size, "%llu", nsecs / 1000000000); return; } wallclock_secs = (real_time_ts.tv_sec - boot_time_ts.tv_sec) + (real_time_ts.tv_nsec - boot_time_ts.tv_nsec + nsecs) / 1000000000; if (!localtime_r(&wallclock_secs, &load_tm)) { snprintf(buf, size, "%llu", nsecs / 1000000000); return; } if (json_output) strftime(buf, size, "%s", &load_tm); else strftime(buf, size, "%FT%T%z", &load_tm); } static int prog_fd_by_nametag(void *nametag, int **fds, bool tag) { unsigned int id = 0; int fd, nb_fds = 0; void *tmp; int err; while (true) { struct bpf_prog_info info = {}; __u32 len = sizeof(info); err = bpf_prog_get_next_id(id, &id); if (err) { if (errno != ENOENT) { p_err("%s", strerror(errno)); goto err_close_fds; } return nb_fds; } fd = bpf_prog_get_fd_by_id(id); if (fd < 0) { p_err("can't get prog by id (%u): %s", id, strerror(errno)); goto err_close_fds; } err = bpf_obj_get_info_by_fd(fd, &info, &len); if (err) { p_err("can't get prog info (%u): %s", id, strerror(errno)); goto err_close_fd; } if ((tag && memcmp(nametag, info.tag, BPF_TAG_SIZE)) || (!tag && strncmp(nametag, info.name, BPF_OBJ_NAME_LEN))) { close(fd); continue; } if (nb_fds > 0) { tmp = realloc(*fds, (nb_fds + 1) * sizeof(int)); if (!tmp) { p_err("failed to realloc"); goto err_close_fd; } *fds = tmp; } (*fds)[nb_fds++] = fd; } err_close_fd: close(fd); err_close_fds: while (--nb_fds >= 0) close((*fds)[nb_fds]); return -1; } static int prog_parse_fds(int *argc, char ***argv, int **fds) { if (is_prefix(**argv, "id")) { unsigned int id; char *endptr; NEXT_ARGP(); id = strtoul(**argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as ID", **argv); return -1; } NEXT_ARGP(); (*fds)[0] = bpf_prog_get_fd_by_id(id); if ((*fds)[0] < 0) { p_err("get by id (%u): %s", id, strerror(errno)); return -1; } return 1; } else if (is_prefix(**argv, "tag")) { unsigned char tag[BPF_TAG_SIZE]; NEXT_ARGP(); if (sscanf(**argv, BPF_TAG_FMT, tag, tag + 1, tag + 2, tag + 3, tag + 4, tag + 5, tag + 6, tag + 7) != BPF_TAG_SIZE) { p_err("can't parse tag"); return -1; } NEXT_ARGP(); return prog_fd_by_nametag(tag, fds, true); } else if (is_prefix(**argv, "name")) { char *name; NEXT_ARGP(); name = **argv; if (strlen(name) > BPF_OBJ_NAME_LEN - 1) { p_err("can't parse name"); return -1; } NEXT_ARGP(); return prog_fd_by_nametag(name, fds, false); } else if (is_prefix(**argv, "pinned")) { char *path; NEXT_ARGP(); path = **argv; NEXT_ARGP(); (*fds)[0] = open_obj_pinned_any(path, BPF_OBJ_PROG); if ((*fds)[0] < 0) return -1; return 1; } p_err("expected 'id', 'tag', 'name' or 'pinned', got: '%s'?", **argv); return -1; } int prog_parse_fd(int *argc, char ***argv) { int *fds = NULL; int nb_fds, fd; fds = malloc(sizeof(int)); if (!fds) { p_err("mem alloc failed"); return -1; } nb_fds = prog_parse_fds(argc, argv, &fds); if (nb_fds != 1) { if (nb_fds > 1) { p_err("several programs match this handle"); while (nb_fds--) close(fds[nb_fds]); } fd = -1; goto exit_free; } fd = fds[0]; exit_free: free(fds); return fd; } static void show_prog_maps(int fd, u32 num_maps) { struct bpf_prog_info info = {}; __u32 len = sizeof(info); __u32 map_ids[num_maps]; unsigned int i; int err; info.nr_map_ids = num_maps; info.map_ids = ptr_to_u64(map_ids); err = bpf_obj_get_info_by_fd(fd, &info, &len); if (err || !info.nr_map_ids) return; if (json_output) { jsonw_name(json_wtr, "map_ids"); jsonw_start_array(json_wtr); for (i = 0; i < info.nr_map_ids; i++) jsonw_uint(json_wtr, map_ids[i]); jsonw_end_array(json_wtr); } else { printf(" map_ids "); for (i = 0; i < info.nr_map_ids; i++) printf("%u%s", map_ids[i], i == info.nr_map_ids - 1 ? "" : ","); } } static void print_prog_header_json(struct bpf_prog_info *info) { jsonw_uint_field(json_wtr, "id", info->id); if (info->type < ARRAY_SIZE(prog_type_name)) jsonw_string_field(json_wtr, "type", prog_type_name[info->type]); else jsonw_uint_field(json_wtr, "type", info->type); if (*info->name) jsonw_string_field(json_wtr, "name", info->name); jsonw_name(json_wtr, "tag"); jsonw_printf(json_wtr, "\"" BPF_TAG_FMT "\"", info->tag[0], info->tag[1], info->tag[2], info->tag[3], info->tag[4], info->tag[5], info->tag[6], info->tag[7]); jsonw_bool_field(json_wtr, "gpl_compatible", info->gpl_compatible); if (info->run_time_ns) { jsonw_uint_field(json_wtr, "run_time_ns", info->run_time_ns); jsonw_uint_field(json_wtr, "run_cnt", info->run_cnt); } } static void print_prog_json(struct bpf_prog_info *info, int fd) { char *memlock; jsonw_start_object(json_wtr); print_prog_header_json(info); print_dev_json(info->ifindex, info->netns_dev, info->netns_ino); if (info->load_time) { char buf[32]; print_boot_time(info->load_time, buf, sizeof(buf)); /* Piggy back on load_time, since 0 uid is a valid one */ jsonw_name(json_wtr, "loaded_at"); jsonw_printf(json_wtr, "%s", buf); jsonw_uint_field(json_wtr, "uid", info->created_by_uid); } jsonw_uint_field(json_wtr, "bytes_xlated", info->xlated_prog_len); if (info->jited_prog_len) { jsonw_bool_field(json_wtr, "jited", true); jsonw_uint_field(json_wtr, "bytes_jited", info->jited_prog_len); } else { jsonw_bool_field(json_wtr, "jited", false); } memlock = get_fdinfo(fd, "memlock"); if (memlock) jsonw_int_field(json_wtr, "bytes_memlock", atoi(memlock)); free(memlock); if (info->nr_map_ids) show_prog_maps(fd, info->nr_map_ids); if (info->btf_id) jsonw_int_field(json_wtr, "btf_id", info->btf_id); if (!hash_empty(prog_table.table)) { struct pinned_obj *obj; jsonw_name(json_wtr, "pinned"); jsonw_start_array(json_wtr); hash_for_each_possible(prog_table.table, obj, hash, info->id) { if (obj->id == info->id) jsonw_string(json_wtr, obj->path); } jsonw_end_array(json_wtr); } jsonw_end_object(json_wtr); } static void print_prog_header_plain(struct bpf_prog_info *info) { printf("%u: ", info->id); if (info->type < ARRAY_SIZE(prog_type_name)) printf("%s ", prog_type_name[info->type]); else printf("type %u ", info->type); if (*info->name) printf("name %s ", info->name); printf("tag "); fprint_hex(stdout, info->tag, BPF_TAG_SIZE, ""); print_dev_plain(info->ifindex, info->netns_dev, info->netns_ino); printf("%s", info->gpl_compatible ? " gpl" : ""); if (info->run_time_ns) printf(" run_time_ns %lld run_cnt %lld", info->run_time_ns, info->run_cnt); printf("\n"); } static void print_prog_plain(struct bpf_prog_info *info, int fd) { char *memlock; print_prog_header_plain(info); if (info->load_time) { char buf[32]; print_boot_time(info->load_time, buf, sizeof(buf)); /* Piggy back on load_time, since 0 uid is a valid one */ printf("\tloaded_at %s uid %u\n", buf, info->created_by_uid); } printf("\txlated %uB", info->xlated_prog_len); if (info->jited_prog_len) printf(" jited %uB", info->jited_prog_len); else printf(" not jited"); memlock = get_fdinfo(fd, "memlock"); if (memlock) printf(" memlock %sB", memlock); free(memlock); if (info->nr_map_ids) show_prog_maps(fd, info->nr_map_ids); if (!hash_empty(prog_table.table)) { struct pinned_obj *obj; hash_for_each_possible(prog_table.table, obj, hash, info->id) { if (obj->id == info->id) printf("\n\tpinned %s", obj->path); } } if (info->btf_id) printf("\n\tbtf_id %d", info->btf_id); printf("\n"); } static int show_prog(int fd) { struct bpf_prog_info info = {}; __u32 len = sizeof(info); int err; err = bpf_obj_get_info_by_fd(fd, &info, &len); if (err) { p_err("can't get prog info: %s", strerror(errno)); return -1; } if (json_output) print_prog_json(&info, fd); else print_prog_plain(&info, fd); return 0; } static int do_show_subset(int argc, char **argv) { int *fds = NULL; int nb_fds, i; int err = -1; fds = malloc(sizeof(int)); if (!fds) { p_err("mem alloc failed"); return -1; } nb_fds = prog_parse_fds(&argc, &argv, &fds); if (nb_fds < 1) goto exit_free; if (json_output && nb_fds > 1) jsonw_start_array(json_wtr); /* root array */ for (i = 0; i < nb_fds; i++) { err = show_prog(fds[i]); if (err) { for (; i < nb_fds; i++) close(fds[i]); break; } close(fds[i]); } if (json_output && nb_fds > 1) jsonw_end_array(json_wtr); /* root array */ exit_free: free(fds); return err; } static int do_show(int argc, char **argv) { __u32 id = 0; int err; int fd; if (show_pinned) build_pinned_obj_table(&prog_table, BPF_OBJ_PROG); if (argc == 2) return do_show_subset(argc, argv); if (argc) return BAD_ARG(); if (json_output) jsonw_start_array(json_wtr); while (true) { err = bpf_prog_get_next_id(id, &id); if (err) { if (errno == ENOENT) { err = 0; break; } p_err("can't get next program: %s%s", strerror(errno), errno == EINVAL ? " -- kernel too old?" : ""); err = -1; break; } fd = bpf_prog_get_fd_by_id(id); if (fd < 0) { if (errno == ENOENT) continue; p_err("can't get prog by id (%u): %s", id, strerror(errno)); err = -1; break; } err = show_prog(fd); close(fd); if (err) break; } if (json_output) jsonw_end_array(json_wtr); return err; } static int prog_dump(struct bpf_prog_info *info, enum dump_mode mode, char *filepath, bool opcodes, bool visual, bool linum) { struct bpf_prog_linfo *prog_linfo = NULL; const char *disasm_opt = NULL; struct dump_data dd = {}; void *func_info = NULL; struct btf *btf = NULL; char func_sig[1024]; unsigned char *buf; __u32 member_len; ssize_t n; int fd; if (mode == DUMP_JITED) { if (info->jited_prog_len == 0 || !info->jited_prog_insns) { p_info("no instructions returned"); return -1; } buf = (unsigned char *)(info->jited_prog_insns); member_len = info->jited_prog_len; } else { /* DUMP_XLATED */ if (info->xlated_prog_len == 0 || !info->xlated_prog_insns) { p_err("error retrieving insn dump: kernel.kptr_restrict set?"); return -1; } buf = (unsigned char *)info->xlated_prog_insns; member_len = info->xlated_prog_len; } if (info->btf_id && btf__get_from_id(info->btf_id, &btf)) { p_err("failed to get btf"); return -1; } func_info = (void *)info->func_info; if (info->nr_line_info) { prog_linfo = bpf_prog_linfo__new(info); if (!prog_linfo) p_info("error in processing bpf_line_info. continue without it."); } if (filepath) { fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) { p_err("can't open file %s: %s", filepath, strerror(errno)); return -1; } n = write(fd, buf, member_len); close(fd); if (n != member_len) { p_err("error writing output file: %s", n < 0 ? strerror(errno) : "short write"); return -1; } if (json_output) jsonw_null(json_wtr); } else if (mode == DUMP_JITED) { const char *name = NULL; if (info->ifindex) { name = ifindex_to_bfd_params(info->ifindex, info->netns_dev, info->netns_ino, &disasm_opt); if (!name) return -1; } if (info->nr_jited_func_lens && info->jited_func_lens) { struct kernel_sym *sym = NULL; struct bpf_func_info *record; char sym_name[SYM_MAX_NAME]; unsigned char *img = buf; __u64 *ksyms = NULL; __u32 *lens; __u32 i; if (info->nr_jited_ksyms) { kernel_syms_load(&dd); ksyms = (__u64 *) info->jited_ksyms; } if (json_output) jsonw_start_array(json_wtr); lens = (__u32 *) info->jited_func_lens; for (i = 0; i < info->nr_jited_func_lens; i++) { if (ksyms) { sym = kernel_syms_search(&dd, ksyms[i]); if (sym) sprintf(sym_name, "%s", sym->name); else sprintf(sym_name, "0x%016llx", ksyms[i]); } else { strcpy(sym_name, "unknown"); } if (func_info) { record = func_info + i * info->func_info_rec_size; btf_dumper_type_only(btf, record->type_id, func_sig, sizeof(func_sig)); } if (json_output) { jsonw_start_object(json_wtr); if (func_info && func_sig[0] != '\0') { jsonw_name(json_wtr, "proto"); jsonw_string(json_wtr, func_sig); } jsonw_name(json_wtr, "name"); jsonw_string(json_wtr, sym_name); jsonw_name(json_wtr, "insns"); } else { if (func_info && func_sig[0] != '\0') printf("%s:\n", func_sig); printf("%s:\n", sym_name); } disasm_print_insn(img, lens[i], opcodes, name, disasm_opt, btf, prog_linfo, ksyms[i], i, linum); img += lens[i]; if (json_output) jsonw_end_object(json_wtr); else printf("\n"); } if (json_output) jsonw_end_array(json_wtr); } else { disasm_print_insn(buf, member_len, opcodes, name, disasm_opt, btf, NULL, 0, 0, false); } } else if (visual) { if (json_output) jsonw_null(json_wtr); else dump_xlated_cfg(buf, member_len); } else { kernel_syms_load(&dd); dd.nr_jited_ksyms = info->nr_jited_ksyms; dd.jited_ksyms = (__u64 *) info->jited_ksyms; dd.btf = btf; dd.func_info = func_info; dd.finfo_rec_size = info->func_info_rec_size; dd.prog_linfo = prog_linfo; if (json_output) dump_xlated_json(&dd, buf, member_len, opcodes, linum); else dump_xlated_plain(&dd, buf, member_len, opcodes, linum); kernel_syms_destroy(&dd); } return 0; } static int do_dump(int argc, char **argv) { struct bpf_prog_info_linear *info_linear; char *filepath = NULL; bool opcodes = false; bool visual = false; enum dump_mode mode; bool linum = false; int *fds = NULL; int nb_fds, i = 0; int err = -1; __u64 arrays; if (is_prefix(*argv, "jited")) { if (disasm_init()) return -1; mode = DUMP_JITED; } else if (is_prefix(*argv, "xlated")) { mode = DUMP_XLATED; } else { p_err("expected 'xlated' or 'jited', got: %s", *argv); return -1; } NEXT_ARG(); if (argc < 2) usage(); fds = malloc(sizeof(int)); if (!fds) { p_err("mem alloc failed"); return -1; } nb_fds = prog_parse_fds(&argc, &argv, &fds); if (nb_fds < 1) goto exit_free; if (is_prefix(*argv, "file")) { NEXT_ARG(); if (!argc) { p_err("expected file path"); goto exit_close; } if (nb_fds > 1) { p_err("several programs matched"); goto exit_close; } filepath = *argv; NEXT_ARG(); } else if (is_prefix(*argv, "opcodes")) { opcodes = true; NEXT_ARG(); } else if (is_prefix(*argv, "visual")) { if (nb_fds > 1) { p_err("several programs matched"); goto exit_close; } visual = true; NEXT_ARG(); } else if (is_prefix(*argv, "linum")) { linum = true; NEXT_ARG(); } if (argc) { usage(); goto exit_close; } if (mode == DUMP_JITED) arrays = 1UL << BPF_PROG_INFO_JITED_INSNS; else arrays = 1UL << BPF_PROG_INFO_XLATED_INSNS; arrays |= 1UL << BPF_PROG_INFO_JITED_KSYMS; arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS; arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO; arrays |= 1UL << BPF_PROG_INFO_LINE_INFO; arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO; if (json_output && nb_fds > 1) jsonw_start_array(json_wtr); /* root array */ for (i = 0; i < nb_fds; i++) { info_linear = bpf_program__get_prog_info_linear(fds[i], arrays); if (IS_ERR_OR_NULL(info_linear)) { p_err("can't get prog info: %s", strerror(errno)); break; } if (json_output && nb_fds > 1) { jsonw_start_object(json_wtr); /* prog object */ print_prog_header_json(&info_linear->info); jsonw_name(json_wtr, "insns"); } else if (nb_fds > 1) { print_prog_header_plain(&info_linear->info); } err = prog_dump(&info_linear->info, mode, filepath, opcodes, visual, linum); if (json_output && nb_fds > 1) jsonw_end_object(json_wtr); /* prog object */ else if (i != nb_fds - 1 && nb_fds > 1) printf("\n"); free(info_linear); if (err) break; close(fds[i]); } if (json_output && nb_fds > 1) jsonw_end_array(json_wtr); /* root array */ exit_close: for (; i < nb_fds; i++) close(fds[i]); exit_free: free(fds); return err; } static int do_pin(int argc, char **argv) { int err; err = do_pin_any(argc, argv, prog_parse_fd); if (!err && json_output) jsonw_null(json_wtr); return err; } struct map_replace { int idx; int fd; char *name; }; static int map_replace_compar(const void *p1, const void *p2) { const struct map_replace *a = p1, *b = p2; return a->idx - b->idx; } static int parse_attach_detach_args(int argc, char **argv, int *progfd, enum bpf_attach_type *attach_type, int *mapfd) { if (!REQ_ARGS(3)) return -EINVAL; *progfd = prog_parse_fd(&argc, &argv); if (*progfd < 0) return *progfd; *attach_type = parse_attach_type(*argv); if (*attach_type == __MAX_BPF_ATTACH_TYPE) { p_err("invalid attach/detach type"); return -EINVAL; } if (*attach_type == BPF_FLOW_DISSECTOR) { *mapfd = -1; return 0; } NEXT_ARG(); if (!REQ_ARGS(2)) return -EINVAL; *mapfd = map_parse_fd(&argc, &argv); if (*mapfd < 0) return *mapfd; return 0; } static int do_attach(int argc, char **argv) { enum bpf_attach_type attach_type; int err, progfd; int mapfd; err = parse_attach_detach_args(argc, argv, &progfd, &attach_type, &mapfd); if (err) return err; err = bpf_prog_attach(progfd, mapfd, attach_type, 0); if (err) { p_err("failed prog attach to map"); return -EINVAL; } if (json_output) jsonw_null(json_wtr); return 0; } static int do_detach(int argc, char **argv) { enum bpf_attach_type attach_type; int err, progfd; int mapfd; err = parse_attach_detach_args(argc, argv, &progfd, &attach_type, &mapfd); if (err) return err; err = bpf_prog_detach2(progfd, mapfd, attach_type); if (err) { p_err("failed prog detach from map"); return -EINVAL; } if (json_output) jsonw_null(json_wtr); return 0; } static int check_single_stdin(char *file_data_in, char *file_ctx_in) { if (file_data_in && file_ctx_in && !strcmp(file_data_in, "-") && !strcmp(file_ctx_in, "-")) { p_err("cannot use standard input for both data_in and ctx_in"); return -1; } return 0; } static int get_run_data(const char *fname, void **data_ptr, unsigned int *size) { size_t block_size = 256; size_t buf_size = block_size; size_t nb_read = 0; void *tmp; FILE *f; if (!fname) { *data_ptr = NULL; *size = 0; return 0; } if (!strcmp(fname, "-")) f = stdin; else f = fopen(fname, "r"); if (!f) { p_err("failed to open %s: %s", fname, strerror(errno)); return -1; } *data_ptr = malloc(block_size); if (!*data_ptr) { p_err("failed to allocate memory for data_in/ctx_in: %s", strerror(errno)); goto err_fclose; } while ((nb_read += fread(*data_ptr + nb_read, 1, block_size, f))) { if (feof(f)) break; if (ferror(f)) { p_err("failed to read data_in/ctx_in from %s: %s", fname, strerror(errno)); goto err_free; } if (nb_read > buf_size - block_size) { if (buf_size == UINT32_MAX) { p_err("data_in/ctx_in is too long (max: %d)", UINT32_MAX); goto err_free; } /* No space for fread()-ing next chunk; realloc() */ buf_size *= 2; tmp = realloc(*data_ptr, buf_size); if (!tmp) { p_err("failed to reallocate data_in/ctx_in: %s", strerror(errno)); goto err_free; } *data_ptr = tmp; } } if (f != stdin) fclose(f); *size = nb_read; return 0; err_free: free(*data_ptr); *data_ptr = NULL; err_fclose: if (f != stdin) fclose(f); return -1; } static void hex_print(void *data, unsigned int size, FILE *f) { size_t i, j; char c; for (i = 0; i < size; i += 16) { /* Row offset */ fprintf(f, "%07zx\t", i); /* Hexadecimal values */ for (j = i; j < i + 16 && j < size; j++) fprintf(f, "%02x%s", *(uint8_t *)(data + j), j % 2 ? " " : ""); for (; j < i + 16; j++) fprintf(f, " %s", j % 2 ? " " : ""); /* ASCII values (if relevant), '.' otherwise */ fprintf(f, "| "); for (j = i; j < i + 16 && j < size; j++) { c = *(char *)(data + j); if (c < ' ' || c > '~') c = '.'; fprintf(f, "%c%s", c, j == i + 7 ? " " : ""); } fprintf(f, "\n"); } } static int print_run_output(void *data, unsigned int size, const char *fname, const char *json_key) { size_t nb_written; FILE *f; if (!fname) return 0; if (!strcmp(fname, "-")) { f = stdout; if (json_output) { jsonw_name(json_wtr, json_key); print_data_json(data, size); } else { hex_print(data, size, f); } return 0; } f = fopen(fname, "w"); if (!f) { p_err("failed to open %s: %s", fname, strerror(errno)); return -1; } nb_written = fwrite(data, 1, size, f); fclose(f); if (nb_written != size) { p_err("failed to write output data/ctx: %s", strerror(errno)); return -1; } return 0; } static int alloc_run_data(void **data_ptr, unsigned int size_out) { *data_ptr = calloc(size_out, 1); if (!*data_ptr) { p_err("failed to allocate memory for output data/ctx: %s", strerror(errno)); return -1; } return 0; } static int do_run(int argc, char **argv) { char *data_fname_in = NULL, *data_fname_out = NULL; char *ctx_fname_in = NULL, *ctx_fname_out = NULL; struct bpf_prog_test_run_attr test_attr = {0}; const unsigned int default_size = SZ_32K; void *data_in = NULL, *data_out = NULL; void *ctx_in = NULL, *ctx_out = NULL; unsigned int repeat = 1; int fd, err; if (!REQ_ARGS(4)) return -1; fd = prog_parse_fd(&argc, &argv); if (fd < 0) return -1; while (argc) { if (detect_common_prefix(*argv, "data_in", "data_out", "data_size_out", NULL)) return -1; if (detect_common_prefix(*argv, "ctx_in", "ctx_out", "ctx_size_out", NULL)) return -1; if (is_prefix(*argv, "data_in")) { NEXT_ARG(); if (!REQ_ARGS(1)) return -1; data_fname_in = GET_ARG(); if (check_single_stdin(data_fname_in, ctx_fname_in)) return -1; } else if (is_prefix(*argv, "data_out")) { NEXT_ARG(); if (!REQ_ARGS(1)) return -1; data_fname_out = GET_ARG(); } else if (is_prefix(*argv, "data_size_out")) { char *endptr; NEXT_ARG(); if (!REQ_ARGS(1)) return -1; test_attr.data_size_out = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as output data size", *argv); return -1; } NEXT_ARG(); } else if (is_prefix(*argv, "ctx_in")) { NEXT_ARG(); if (!REQ_ARGS(1)) return -1; ctx_fname_in = GET_ARG(); if (check_single_stdin(data_fname_in, ctx_fname_in)) return -1; } else if (is_prefix(*argv, "ctx_out")) { NEXT_ARG(); if (!REQ_ARGS(1)) return -1; ctx_fname_out = GET_ARG(); } else if (is_prefix(*argv, "ctx_size_out")) { char *endptr; NEXT_ARG(); if (!REQ_ARGS(1)) return -1; test_attr.ctx_size_out = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as output context size", *argv); return -1; } NEXT_ARG(); } else if (is_prefix(*argv, "repeat")) { char *endptr; NEXT_ARG(); if (!REQ_ARGS(1)) return -1; repeat = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as repeat number", *argv); return -1; } NEXT_ARG(); } else { p_err("expected no more arguments, 'data_in', 'data_out', 'data_size_out', 'ctx_in', 'ctx_out', 'ctx_size_out' or 'repeat', got: '%s'?", *argv); return -1; } } err = get_run_data(data_fname_in, &data_in, &test_attr.data_size_in); if (err) return -1; if (data_in) { if (!test_attr.data_size_out) test_attr.data_size_out = default_size; err = alloc_run_data(&data_out, test_attr.data_size_out); if (err) goto free_data_in; } err = get_run_data(ctx_fname_in, &ctx_in, &test_attr.ctx_size_in); if (err) goto free_data_out; if (ctx_in) { if (!test_attr.ctx_size_out) test_attr.ctx_size_out = default_size; err = alloc_run_data(&ctx_out, test_attr.ctx_size_out); if (err) goto free_ctx_in; } test_attr.prog_fd = fd; test_attr.repeat = repeat; test_attr.data_in = data_in; test_attr.data_out = data_out; test_attr.ctx_in = ctx_in; test_attr.ctx_out = ctx_out; err = bpf_prog_test_run_xattr(&test_attr); if (err) { p_err("failed to run program: %s", strerror(errno)); goto free_ctx_out; } err = 0; if (json_output) jsonw_start_object(json_wtr); /* root */ /* Do not exit on errors occurring when printing output data/context, * we still want to print return value and duration for program run. */ if (test_attr.data_size_out) err += print_run_output(test_attr.data_out, test_attr.data_size_out, data_fname_out, "data_out"); if (test_attr.ctx_size_out) err += print_run_output(test_attr.ctx_out, test_attr.ctx_size_out, ctx_fname_out, "ctx_out"); if (json_output) { jsonw_uint_field(json_wtr, "retval", test_attr.retval); jsonw_uint_field(json_wtr, "duration", test_attr.duration); jsonw_end_object(json_wtr); /* root */ } else { fprintf(stdout, "Return value: %u, duration%s: %uns\n", test_attr.retval, repeat > 1 ? " (average)" : "", test_attr.duration); } free_ctx_out: free(ctx_out); free_ctx_in: free(ctx_in); free_data_out: free(data_out); free_data_in: free(data_in); return err; } static int get_prog_type_by_name(const char *name, enum bpf_prog_type *prog_type, enum bpf_attach_type *expected_attach_type) { libbpf_print_fn_t print_backup; int ret; ret = libbpf_prog_type_by_name(name, prog_type, expected_attach_type); if (!ret) return ret; /* libbpf_prog_type_by_name() failed, let's re-run with debug level */ print_backup = libbpf_set_print(print_all_levels); ret = libbpf_prog_type_by_name(name, prog_type, expected_attach_type); libbpf_set_print(print_backup); return ret; } static int load_with_options(int argc, char **argv, bool first_prog_only) { enum bpf_prog_type common_prog_type = BPF_PROG_TYPE_UNSPEC; DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts, .relaxed_maps = relaxed_maps, ); struct bpf_object_load_attr load_attr = { 0 }; enum bpf_attach_type expected_attach_type; struct map_replace *map_replace = NULL; struct bpf_program *prog = NULL, *pos; unsigned int old_map_fds = 0; const char *pinmaps = NULL; struct bpf_object *obj; struct bpf_map *map; const char *pinfile; unsigned int i, j; __u32 ifindex = 0; const char *file; int idx, err; if (!REQ_ARGS(2)) return -1; file = GET_ARG(); pinfile = GET_ARG(); while (argc) { if (is_prefix(*argv, "type")) { char *type; NEXT_ARG(); if (common_prog_type != BPF_PROG_TYPE_UNSPEC) { p_err("program type already specified"); goto err_free_reuse_maps; } if (!REQ_ARGS(1)) goto err_free_reuse_maps; /* Put a '/' at the end of type to appease libbpf */ type = malloc(strlen(*argv) + 2); if (!type) { p_err("mem alloc failed"); goto err_free_reuse_maps; } *type = 0; strcat(type, *argv); strcat(type, "/"); err = get_prog_type_by_name(type, &common_prog_type, &expected_attach_type); free(type); if (err < 0) goto err_free_reuse_maps; NEXT_ARG(); } else if (is_prefix(*argv, "map")) { void *new_map_replace; char *endptr, *name; int fd; NEXT_ARG(); if (!REQ_ARGS(4)) goto err_free_reuse_maps; if (is_prefix(*argv, "idx")) { NEXT_ARG(); idx = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as IDX", *argv); goto err_free_reuse_maps; } name = NULL; } else if (is_prefix(*argv, "name")) { NEXT_ARG(); name = *argv; idx = -1; } else { p_err("expected 'idx' or 'name', got: '%s'?", *argv); goto err_free_reuse_maps; } NEXT_ARG(); fd = map_parse_fd(&argc, &argv); if (fd < 0) goto err_free_reuse_maps; new_map_replace = reallocarray(map_replace, old_map_fds + 1, sizeof(*map_replace)); if (!new_map_replace) { p_err("mem alloc failed"); goto err_free_reuse_maps; } map_replace = new_map_replace; map_replace[old_map_fds].idx = idx; map_replace[old_map_fds].name = name; map_replace[old_map_fds].fd = fd; old_map_fds++; } else if (is_prefix(*argv, "dev")) { NEXT_ARG(); if (ifindex) { p_err("offload device already specified"); goto err_free_reuse_maps; } if (!REQ_ARGS(1)) goto err_free_reuse_maps; ifindex = if_nametoindex(*argv); if (!ifindex) { p_err("unrecognized netdevice '%s': %s", *argv, strerror(errno)); goto err_free_reuse_maps; } NEXT_ARG(); } else if (is_prefix(*argv, "pinmaps")) { NEXT_ARG(); if (!REQ_ARGS(1)) goto err_free_reuse_maps; pinmaps = GET_ARG(); } else { p_err("expected no more arguments, 'type', 'map' or 'dev', got: '%s'?", *argv); goto err_free_reuse_maps; } } set_max_rlimit(); obj = bpf_object__open_file(file, &open_opts); if (IS_ERR_OR_NULL(obj)) { p_err("failed to open object file"); goto err_free_reuse_maps; } bpf_object__for_each_program(pos, obj) { enum bpf_prog_type prog_type = common_prog_type; if (prog_type == BPF_PROG_TYPE_UNSPEC) { const char *sec_name = bpf_program__title(pos, false); err = get_prog_type_by_name(sec_name, &prog_type, &expected_attach_type); if (err < 0) goto err_close_obj; } bpf_program__set_ifindex(pos, ifindex); bpf_program__set_type(pos, prog_type); bpf_program__set_expected_attach_type(pos, expected_attach_type); } qsort(map_replace, old_map_fds, sizeof(*map_replace), map_replace_compar); /* After the sort maps by name will be first on the list, because they * have idx == -1. Resolve them. */ j = 0; while (j < old_map_fds && map_replace[j].name) { i = 0; bpf_object__for_each_map(map, obj) { if (!strcmp(bpf_map__name(map), map_replace[j].name)) { map_replace[j].idx = i; break; } i++; } if (map_replace[j].idx == -1) { p_err("unable to find map '%s'", map_replace[j].name); goto err_close_obj; } j++; } /* Resort if any names were resolved */ if (j) qsort(map_replace, old_map_fds, sizeof(*map_replace), map_replace_compar); /* Set ifindex and name reuse */ j = 0; idx = 0; bpf_object__for_each_map(map, obj) { if (!bpf_map__is_offload_neutral(map)) bpf_map__set_ifindex(map, ifindex); if (j < old_map_fds && idx == map_replace[j].idx) { err = bpf_map__reuse_fd(map, map_replace[j++].fd); if (err) { p_err("unable to set up map reuse: %d", err); goto err_close_obj; } /* Next reuse wants to apply to the same map */ if (j < old_map_fds && map_replace[j].idx == idx) { p_err("replacement for map idx %d specified more than once", idx); goto err_close_obj; } } idx++; } if (j < old_map_fds) { p_err("map idx '%d' not used", map_replace[j].idx); goto err_close_obj; } load_attr.obj = obj; if (verifier_logs) /* log_level1 + log_level2 + stats, but not stable UAPI */ load_attr.log_level = 1 + 2 + 4; err = bpf_object__load_xattr(&load_attr); if (err) { p_err("failed to load object file"); goto err_close_obj; } err = mount_bpffs_for_pin(pinfile); if (err) goto err_close_obj; if (first_prog_only) { prog = bpf_program__next(NULL, obj); if (!prog) { p_err("object file doesn't contain any bpf program"); goto err_close_obj; } err = bpf_obj_pin(bpf_program__fd(prog), pinfile); if (err) { p_err("failed to pin program %s", bpf_program__title(prog, false)); goto err_close_obj; } } else { err = bpf_object__pin_programs(obj, pinfile); if (err) { p_err("failed to pin all programs"); goto err_close_obj; } } if (pinmaps) { err = bpf_object__pin_maps(obj, pinmaps); if (err) { p_err("failed to pin all maps"); goto err_unpin; } } if (json_output) jsonw_null(json_wtr); bpf_object__close(obj); for (i = 0; i < old_map_fds; i++) close(map_replace[i].fd); free(map_replace); return 0; err_unpin: if (first_prog_only) unlink(pinfile); else bpf_object__unpin_programs(obj, pinfile); err_close_obj: bpf_object__close(obj); err_free_reuse_maps: for (i = 0; i < old_map_fds; i++) close(map_replace[i].fd); free(map_replace); return -1; } static int do_load(int argc, char **argv) { return load_with_options(argc, argv, true); } static int do_loadall(int argc, char **argv) { return load_with_options(argc, argv, false); } #ifdef BPFTOOL_WITHOUT_SKELETONS static int do_profile(int argc, char **argv) { p_err("bpftool prog profile command is not supported. Please build bpftool with clang >= 10.0.0"); return 0; } #else /* BPFTOOL_WITHOUT_SKELETONS */ #include "profiler.skel.h" struct profile_metric { const char *name; struct bpf_perf_event_value val; struct perf_event_attr attr; bool selected; /* calculate ratios like instructions per cycle */ const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */ const char *ratio_desc; const float ratio_mul; } metrics[] = { { .name = "cycles", .attr = { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES, .exclude_user = 1, }, }, { .name = "instructions", .attr = { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS, .exclude_user = 1, }, .ratio_metric = 1, .ratio_desc = "insns per cycle", .ratio_mul = 1.0, }, { .name = "l1d_loads", .attr = { .type = PERF_TYPE_HW_CACHE, .config = PERF_COUNT_HW_CACHE_L1D | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16), .exclude_user = 1, }, }, { .name = "llc_misses", .attr = { .type = PERF_TYPE_HW_CACHE, .config = PERF_COUNT_HW_CACHE_LL | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16), .exclude_user = 1 }, .ratio_metric = 2, .ratio_desc = "LLC misses per million insns", .ratio_mul = 1e6, }, }; static __u64 profile_total_count; #define MAX_NUM_PROFILE_METRICS 4 static int profile_parse_metrics(int argc, char **argv) { unsigned int metric_cnt; int selected_cnt = 0; unsigned int i; metric_cnt = sizeof(metrics) / sizeof(struct profile_metric); while (argc > 0) { for (i = 0; i < metric_cnt; i++) { if (is_prefix(argv[0], metrics[i].name)) { if (!metrics[i].selected) selected_cnt++; metrics[i].selected = true; break; } } if (i == metric_cnt) { p_err("unknown metric %s", argv[0]); return -1; } NEXT_ARG(); } if (selected_cnt > MAX_NUM_PROFILE_METRICS) { p_err("too many (%d) metrics, please specify no more than %d metrics at at time", selected_cnt, MAX_NUM_PROFILE_METRICS); return -1; } return selected_cnt; } static void profile_read_values(struct profiler_bpf *obj) { __u32 m, cpu, num_cpu = obj->rodata->num_cpu; int reading_map_fd, count_map_fd; __u64 counts[num_cpu]; __u32 key = 0; int err; reading_map_fd = bpf_map__fd(obj->maps.accum_readings); count_map_fd = bpf_map__fd(obj->maps.counts); if (reading_map_fd < 0 || count_map_fd < 0) { p_err("failed to get fd for map"); return; } err = bpf_map_lookup_elem(count_map_fd, &key, counts); if (err) { p_err("failed to read count_map: %s", strerror(errno)); return; } profile_total_count = 0; for (cpu = 0; cpu < num_cpu; cpu++) profile_total_count += counts[cpu]; for (m = 0; m < ARRAY_SIZE(metrics); m++) { struct bpf_perf_event_value values[num_cpu]; if (!metrics[m].selected) continue; err = bpf_map_lookup_elem(reading_map_fd, &key, values); if (err) { p_err("failed to read reading_map: %s", strerror(errno)); return; } for (cpu = 0; cpu < num_cpu; cpu++) { metrics[m].val.counter += values[cpu].counter; metrics[m].val.enabled += values[cpu].enabled; metrics[m].val.running += values[cpu].running; } key++; } } static void profile_print_readings_json(void) { __u32 m; jsonw_start_array(json_wtr); for (m = 0; m < ARRAY_SIZE(metrics); m++) { if (!metrics[m].selected) continue; jsonw_start_object(json_wtr); jsonw_string_field(json_wtr, "metric", metrics[m].name); jsonw_lluint_field(json_wtr, "run_cnt", profile_total_count); jsonw_lluint_field(json_wtr, "value", metrics[m].val.counter); jsonw_lluint_field(json_wtr, "enabled", metrics[m].val.enabled); jsonw_lluint_field(json_wtr, "running", metrics[m].val.running); jsonw_end_object(json_wtr); } jsonw_end_array(json_wtr); } static void profile_print_readings_plain(void) { __u32 m; printf("\n%18llu %-20s\n", profile_total_count, "run_cnt"); for (m = 0; m < ARRAY_SIZE(metrics); m++) { struct bpf_perf_event_value *val = &metrics[m].val; int r; if (!metrics[m].selected) continue; printf("%18llu %-20s", val->counter, metrics[m].name); r = metrics[m].ratio_metric - 1; if (r >= 0 && metrics[r].selected && metrics[r].val.counter > 0) { printf("# %8.2f %-30s", val->counter * metrics[m].ratio_mul / metrics[r].val.counter, metrics[m].ratio_desc); } else { printf("%-41s", ""); } if (val->enabled > val->running) printf("(%4.2f%%)", val->running * 100.0 / val->enabled); printf("\n"); } } static void profile_print_readings(void) { if (json_output) profile_print_readings_json(); else profile_print_readings_plain(); } static char *profile_target_name(int tgt_fd) { struct bpf_prog_info_linear *info_linear; struct bpf_func_info *func_info; const struct btf_type *t; char *name = NULL; struct btf *btf; info_linear = bpf_program__get_prog_info_linear( tgt_fd, 1UL << BPF_PROG_INFO_FUNC_INFO); if (IS_ERR_OR_NULL(info_linear)) { p_err("failed to get info_linear for prog FD %d", tgt_fd); return NULL; } if (info_linear->info.btf_id == 0 || btf__get_from_id(info_linear->info.btf_id, &btf)) { p_err("prog FD %d doesn't have valid btf", tgt_fd); goto out; } func_info = (struct bpf_func_info *)(info_linear->info.func_info); t = btf__type_by_id(btf, func_info[0].type_id); if (!t) { p_err("btf %d doesn't have type %d", info_linear->info.btf_id, func_info[0].type_id); goto out; } name = strdup(btf__name_by_offset(btf, t->name_off)); out: free(info_linear); return name; } static struct profiler_bpf *profile_obj; static int profile_tgt_fd = -1; static char *profile_tgt_name; static int *profile_perf_events; static int profile_perf_event_cnt; static void profile_close_perf_events(struct profiler_bpf *obj) { int i; for (i = profile_perf_event_cnt - 1; i >= 0; i--) close(profile_perf_events[i]); free(profile_perf_events); profile_perf_event_cnt = 0; } static int profile_open_perf_events(struct profiler_bpf *obj) { unsigned int cpu, m; int map_fd, pmu_fd; profile_perf_events = calloc( sizeof(int), obj->rodata->num_cpu * obj->rodata->num_metric); if (!profile_perf_events) { p_err("failed to allocate memory for perf_event array: %s", strerror(errno)); return -1; } map_fd = bpf_map__fd(obj->maps.events); if (map_fd < 0) { p_err("failed to get fd for events map"); return -1; } for (m = 0; m < ARRAY_SIZE(metrics); m++) { if (!metrics[m].selected) continue; for (cpu = 0; cpu < obj->rodata->num_cpu; cpu++) { pmu_fd = syscall(__NR_perf_event_open, &metrics[m].attr, -1/*pid*/, cpu, -1/*group_fd*/, 0); if (pmu_fd < 0 || bpf_map_update_elem(map_fd, &profile_perf_event_cnt, &pmu_fd, BPF_ANY) || ioctl(pmu_fd, PERF_EVENT_IOC_ENABLE, 0)) { p_err("failed to create event %s on cpu %d", metrics[m].name, cpu); return -1; } profile_perf_events[profile_perf_event_cnt++] = pmu_fd; } } return 0; } static void profile_print_and_cleanup(void) { profile_close_perf_events(profile_obj); profile_read_values(profile_obj); profile_print_readings(); profiler_bpf__destroy(profile_obj); close(profile_tgt_fd); free(profile_tgt_name); } static void int_exit(int signo) { profile_print_and_cleanup(); exit(0); } static int do_profile(int argc, char **argv) { int num_metric, num_cpu, err = -1; struct bpf_program *prog; unsigned long duration; char *endptr; /* we at least need two args for the prog and one metric */ if (!REQ_ARGS(3)) return -EINVAL; /* parse target fd */ profile_tgt_fd = prog_parse_fd(&argc, &argv); if (profile_tgt_fd < 0) { p_err("failed to parse fd"); return -1; } /* parse profiling optional duration */ if (argc > 2 && is_prefix(argv[0], "duration")) { NEXT_ARG(); duration = strtoul(*argv, &endptr, 0); if (*endptr) usage(); NEXT_ARG(); } else { duration = UINT_MAX; } num_metric = profile_parse_metrics(argc, argv); if (num_metric <= 0) goto out; num_cpu = libbpf_num_possible_cpus(); if (num_cpu <= 0) { p_err("failed to identify number of CPUs"); goto out; } profile_obj = profiler_bpf__open(); if (!profile_obj) { p_err("failed to open and/or load BPF object"); goto out; } profile_obj->rodata->num_cpu = num_cpu; profile_obj->rodata->num_metric = num_metric; /* adjust map sizes */ bpf_map__resize(profile_obj->maps.events, num_metric * num_cpu); bpf_map__resize(profile_obj->maps.fentry_readings, num_metric); bpf_map__resize(profile_obj->maps.accum_readings, num_metric); bpf_map__resize(profile_obj->maps.counts, 1); /* change target name */ profile_tgt_name = profile_target_name(profile_tgt_fd); if (!profile_tgt_name) goto out; bpf_object__for_each_program(prog, profile_obj->obj) { err = bpf_program__set_attach_target(prog, profile_tgt_fd, profile_tgt_name); if (err) { p_err("failed to set attach target\n"); goto out; } } set_max_rlimit(); err = profiler_bpf__load(profile_obj); if (err) { p_err("failed to load profile_obj"); goto out; } err = profile_open_perf_events(profile_obj); if (err) goto out; err = profiler_bpf__attach(profile_obj); if (err) { p_err("failed to attach profile_obj"); goto out; } signal(SIGINT, int_exit); sleep(duration); profile_print_and_cleanup(); return 0; out: profile_close_perf_events(profile_obj); if (profile_obj) profiler_bpf__destroy(profile_obj); close(profile_tgt_fd); free(profile_tgt_name); return err; } #endif /* BPFTOOL_WITHOUT_SKELETONS */ static int do_help(int argc, char **argv) { if (json_output) { jsonw_null(json_wtr); return 0; } fprintf(stderr, "Usage: %s %s { show | list } [PROG]\n" " %s %s dump xlated PROG [{ file FILE | opcodes | visual | linum }]\n" " %s %s dump jited PROG [{ file FILE | opcodes | linum }]\n" " %s %s pin PROG FILE\n" " %s %s { load | loadall } OBJ PATH \\\n" " [type TYPE] [dev NAME] \\\n" " [map { idx IDX | name NAME } MAP]\\\n" " [pinmaps MAP_DIR]\n" " %s %s attach PROG ATTACH_TYPE [MAP]\n" " %s %s detach PROG ATTACH_TYPE [MAP]\n" " %s %s run PROG \\\n" " data_in FILE \\\n" " [data_out FILE [data_size_out L]] \\\n" " [ctx_in FILE [ctx_out FILE [ctx_size_out M]]] \\\n" " [repeat N]\n" " %s %s profile PROG [duration DURATION] METRICs\n" " %s %s tracelog\n" " %s %s help\n" "\n" " " HELP_SPEC_MAP "\n" " " HELP_SPEC_PROGRAM "\n" " TYPE := { socket | kprobe | kretprobe | classifier | action |\n" " tracepoint | raw_tracepoint | xdp | perf_event | cgroup/skb |\n" " cgroup/sock | cgroup/dev | lwt_in | lwt_out | lwt_xmit |\n" " lwt_seg6local | sockops | sk_skb | sk_msg | lirc_mode2 |\n" " sk_reuseport | flow_dissector | cgroup/sysctl |\n" " cgroup/bind4 | cgroup/bind6 | cgroup/post_bind4 |\n" " cgroup/post_bind6 | cgroup/connect4 | cgroup/connect6 |\n" " cgroup/sendmsg4 | cgroup/sendmsg6 | cgroup/recvmsg4 |\n" " cgroup/recvmsg6 | cgroup/getsockopt | cgroup/setsockopt |\n" " struct_ops | fentry | fexit | freplace }\n" " ATTACH_TYPE := { msg_verdict | stream_verdict | stream_parser |\n" " flow_dissector }\n" " METRIC := { cycles | instructions | l1d_loads | llc_misses }\n" " " HELP_SPEC_OPTIONS "\n" "", bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2]); return 0; } static const struct cmd cmds[] = { { "show", do_show }, { "list", do_show }, { "help", do_help }, { "dump", do_dump }, { "pin", do_pin }, { "load", do_load }, { "loadall", do_loadall }, { "attach", do_attach }, { "detach", do_detach }, { "tracelog", do_tracelog }, { "run", do_run }, { "profile", do_profile }, { 0 } }; int do_prog(int argc, char **argv) { return cmd_select(cmds, argc, argv, do_help); }