diff options
Diffstat (limited to 'tools/bpf/bpftool/btf.c')
-rw-r--r-- | tools/bpf/bpftool/btf.c | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c new file mode 100644 index 000000000..592803af9 --- /dev/null +++ b/tools/bpf/bpftool/btf.c @@ -0,0 +1,938 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* Copyright (C) 2019 Facebook */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/err.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <bpf/bpf.h> +#include <bpf/btf.h> +#include <bpf/libbpf.h> +#include <linux/btf.h> +#include <linux/hashtable.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "json_writer.h" +#include "main.h" + +static const char * const btf_kind_str[NR_BTF_KINDS] = { + [BTF_KIND_UNKN] = "UNKNOWN", + [BTF_KIND_INT] = "INT", + [BTF_KIND_PTR] = "PTR", + [BTF_KIND_ARRAY] = "ARRAY", + [BTF_KIND_STRUCT] = "STRUCT", + [BTF_KIND_UNION] = "UNION", + [BTF_KIND_ENUM] = "ENUM", + [BTF_KIND_FWD] = "FWD", + [BTF_KIND_TYPEDEF] = "TYPEDEF", + [BTF_KIND_VOLATILE] = "VOLATILE", + [BTF_KIND_CONST] = "CONST", + [BTF_KIND_RESTRICT] = "RESTRICT", + [BTF_KIND_FUNC] = "FUNC", + [BTF_KIND_FUNC_PROTO] = "FUNC_PROTO", + [BTF_KIND_VAR] = "VAR", + [BTF_KIND_DATASEC] = "DATASEC", +}; + +struct btf_attach_table { + DECLARE_HASHTABLE(table, 16); +}; + +struct btf_attach_point { + __u32 obj_id; + __u32 btf_id; + struct hlist_node hash; +}; + +static const char *btf_int_enc_str(__u8 encoding) +{ + switch (encoding) { + case 0: + return "(none)"; + case BTF_INT_SIGNED: + return "SIGNED"; + case BTF_INT_CHAR: + return "CHAR"; + case BTF_INT_BOOL: + return "BOOL"; + default: + return "UNKN"; + } +} + +static const char *btf_var_linkage_str(__u32 linkage) +{ + switch (linkage) { + case BTF_VAR_STATIC: + return "static"; + case BTF_VAR_GLOBAL_ALLOCATED: + return "global-alloc"; + default: + return "(unknown)"; + } +} + +static const char *btf_func_linkage_str(const struct btf_type *t) +{ + switch (btf_vlen(t)) { + case BTF_FUNC_STATIC: + return "static"; + case BTF_FUNC_GLOBAL: + return "global"; + case BTF_FUNC_EXTERN: + return "extern"; + default: + return "(unknown)"; + } +} + +static const char *btf_str(const struct btf *btf, __u32 off) +{ + if (!off) + return "(anon)"; + return btf__name_by_offset(btf, off) ? : "(invalid)"; +} + +static int dump_btf_type(const struct btf *btf, __u32 id, + const struct btf_type *t) +{ + json_writer_t *w = json_wtr; + int kind, safe_kind; + + kind = BTF_INFO_KIND(t->info); + safe_kind = kind <= BTF_KIND_MAX ? kind : BTF_KIND_UNKN; + + if (json_output) { + jsonw_start_object(w); + jsonw_uint_field(w, "id", id); + jsonw_string_field(w, "kind", btf_kind_str[safe_kind]); + jsonw_string_field(w, "name", btf_str(btf, t->name_off)); + } else { + printf("[%u] %s '%s'", id, btf_kind_str[safe_kind], + btf_str(btf, t->name_off)); + } + + switch (BTF_INFO_KIND(t->info)) { + case BTF_KIND_INT: { + __u32 v = *(__u32 *)(t + 1); + const char *enc; + + enc = btf_int_enc_str(BTF_INT_ENCODING(v)); + + if (json_output) { + jsonw_uint_field(w, "size", t->size); + jsonw_uint_field(w, "bits_offset", BTF_INT_OFFSET(v)); + jsonw_uint_field(w, "nr_bits", BTF_INT_BITS(v)); + jsonw_string_field(w, "encoding", enc); + } else { + printf(" size=%u bits_offset=%u nr_bits=%u encoding=%s", + t->size, BTF_INT_OFFSET(v), BTF_INT_BITS(v), + enc); + } + break; + } + case BTF_KIND_PTR: + case BTF_KIND_CONST: + case BTF_KIND_VOLATILE: + case BTF_KIND_RESTRICT: + case BTF_KIND_TYPEDEF: + if (json_output) + jsonw_uint_field(w, "type_id", t->type); + else + printf(" type_id=%u", t->type); + break; + case BTF_KIND_ARRAY: { + const struct btf_array *arr = (const void *)(t + 1); + + if (json_output) { + jsonw_uint_field(w, "type_id", arr->type); + jsonw_uint_field(w, "index_type_id", arr->index_type); + jsonw_uint_field(w, "nr_elems", arr->nelems); + } else { + printf(" type_id=%u index_type_id=%u nr_elems=%u", + arr->type, arr->index_type, arr->nelems); + } + break; + } + case BTF_KIND_STRUCT: + case BTF_KIND_UNION: { + const struct btf_member *m = (const void *)(t + 1); + __u16 vlen = BTF_INFO_VLEN(t->info); + int i; + + if (json_output) { + jsonw_uint_field(w, "size", t->size); + jsonw_uint_field(w, "vlen", vlen); + jsonw_name(w, "members"); + jsonw_start_array(w); + } else { + printf(" size=%u vlen=%u", t->size, vlen); + } + for (i = 0; i < vlen; i++, m++) { + const char *name = btf_str(btf, m->name_off); + __u32 bit_off, bit_sz; + + if (BTF_INFO_KFLAG(t->info)) { + bit_off = BTF_MEMBER_BIT_OFFSET(m->offset); + bit_sz = BTF_MEMBER_BITFIELD_SIZE(m->offset); + } else { + bit_off = m->offset; + bit_sz = 0; + } + + if (json_output) { + jsonw_start_object(w); + jsonw_string_field(w, "name", name); + jsonw_uint_field(w, "type_id", m->type); + jsonw_uint_field(w, "bits_offset", bit_off); + if (bit_sz) { + jsonw_uint_field(w, "bitfield_size", + bit_sz); + } + jsonw_end_object(w); + } else { + printf("\n\t'%s' type_id=%u bits_offset=%u", + name, m->type, bit_off); + if (bit_sz) + printf(" bitfield_size=%u", bit_sz); + } + } + if (json_output) + jsonw_end_array(w); + break; + } + case BTF_KIND_ENUM: { + const struct btf_enum *v = (const void *)(t + 1); + __u16 vlen = BTF_INFO_VLEN(t->info); + int i; + + if (json_output) { + jsonw_uint_field(w, "size", t->size); + jsonw_uint_field(w, "vlen", vlen); + jsonw_name(w, "values"); + jsonw_start_array(w); + } else { + printf(" size=%u vlen=%u", t->size, vlen); + } + for (i = 0; i < vlen; i++, v++) { + const char *name = btf_str(btf, v->name_off); + + if (json_output) { + jsonw_start_object(w); + jsonw_string_field(w, "name", name); + jsonw_uint_field(w, "val", v->val); + jsonw_end_object(w); + } else { + printf("\n\t'%s' val=%u", name, v->val); + } + } + if (json_output) + jsonw_end_array(w); + break; + } + case BTF_KIND_FWD: { + const char *fwd_kind = BTF_INFO_KFLAG(t->info) ? "union" + : "struct"; + + if (json_output) + jsonw_string_field(w, "fwd_kind", fwd_kind); + else + printf(" fwd_kind=%s", fwd_kind); + break; + } + case BTF_KIND_FUNC: { + const char *linkage = btf_func_linkage_str(t); + + if (json_output) { + jsonw_uint_field(w, "type_id", t->type); + jsonw_string_field(w, "linkage", linkage); + } else { + printf(" type_id=%u linkage=%s", t->type, linkage); + } + break; + } + case BTF_KIND_FUNC_PROTO: { + const struct btf_param *p = (const void *)(t + 1); + __u16 vlen = BTF_INFO_VLEN(t->info); + int i; + + if (json_output) { + jsonw_uint_field(w, "ret_type_id", t->type); + jsonw_uint_field(w, "vlen", vlen); + jsonw_name(w, "params"); + jsonw_start_array(w); + } else { + printf(" ret_type_id=%u vlen=%u", t->type, vlen); + } + for (i = 0; i < vlen; i++, p++) { + const char *name = btf_str(btf, p->name_off); + + if (json_output) { + jsonw_start_object(w); + jsonw_string_field(w, "name", name); + jsonw_uint_field(w, "type_id", p->type); + jsonw_end_object(w); + } else { + printf("\n\t'%s' type_id=%u", name, p->type); + } + } + if (json_output) + jsonw_end_array(w); + break; + } + case BTF_KIND_VAR: { + const struct btf_var *v = (const void *)(t + 1); + const char *linkage; + + linkage = btf_var_linkage_str(v->linkage); + + if (json_output) { + jsonw_uint_field(w, "type_id", t->type); + jsonw_string_field(w, "linkage", linkage); + } else { + printf(" type_id=%u, linkage=%s", t->type, linkage); + } + break; + } + case BTF_KIND_DATASEC: { + const struct btf_var_secinfo *v = (const void *)(t+1); + __u16 vlen = BTF_INFO_VLEN(t->info); + int i; + + if (json_output) { + jsonw_uint_field(w, "size", t->size); + jsonw_uint_field(w, "vlen", vlen); + jsonw_name(w, "vars"); + jsonw_start_array(w); + } else { + printf(" size=%u vlen=%u", t->size, vlen); + } + for (i = 0; i < vlen; i++, v++) { + if (json_output) { + jsonw_start_object(w); + jsonw_uint_field(w, "type_id", v->type); + jsonw_uint_field(w, "offset", v->offset); + jsonw_uint_field(w, "size", v->size); + jsonw_end_object(w); + } else { + printf("\n\ttype_id=%u offset=%u size=%u", + v->type, v->offset, v->size); + } + } + if (json_output) + jsonw_end_array(w); + break; + } + default: + break; + } + + if (json_output) + jsonw_end_object(json_wtr); + else + printf("\n"); + + return 0; +} + +static int dump_btf_raw(const struct btf *btf, + __u32 *root_type_ids, int root_type_cnt) +{ + const struct btf_type *t; + int i; + + if (json_output) { + jsonw_start_object(json_wtr); + jsonw_name(json_wtr, "types"); + jsonw_start_array(json_wtr); + } + + if (root_type_cnt) { + for (i = 0; i < root_type_cnt; i++) { + t = btf__type_by_id(btf, root_type_ids[i]); + dump_btf_type(btf, root_type_ids[i], t); + } + } else { + int cnt = btf__get_nr_types(btf); + + for (i = 1; i <= cnt; i++) { + t = btf__type_by_id(btf, i); + dump_btf_type(btf, i, t); + } + } + + if (json_output) { + jsonw_end_array(json_wtr); + jsonw_end_object(json_wtr); + } + return 0; +} + +static void __printf(2, 0) btf_dump_printf(void *ctx, + const char *fmt, va_list args) +{ + vfprintf(stdout, fmt, args); +} + +static int dump_btf_c(const struct btf *btf, + __u32 *root_type_ids, int root_type_cnt) +{ + struct btf_dump *d; + int err = 0, i; + + d = btf_dump__new(btf, NULL, NULL, btf_dump_printf); + if (IS_ERR(d)) + return PTR_ERR(d); + + printf("#ifndef __VMLINUX_H__\n"); + printf("#define __VMLINUX_H__\n"); + printf("\n"); + printf("#ifndef BPF_NO_PRESERVE_ACCESS_INDEX\n"); + printf("#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)\n"); + printf("#endif\n\n"); + + if (root_type_cnt) { + for (i = 0; i < root_type_cnt; i++) { + err = btf_dump__dump_type(d, root_type_ids[i]); + if (err) + goto done; + } + } else { + int cnt = btf__get_nr_types(btf); + + for (i = 1; i <= cnt; i++) { + err = btf_dump__dump_type(d, i); + if (err) + goto done; + } + } + + printf("#ifndef BPF_NO_PRESERVE_ACCESS_INDEX\n"); + printf("#pragma clang attribute pop\n"); + printf("#endif\n"); + printf("\n"); + printf("#endif /* __VMLINUX_H__ */\n"); + +done: + btf_dump__free(d); + return err; +} + +static int do_dump(int argc, char **argv) +{ + struct btf *btf = NULL; + __u32 root_type_ids[2]; + int root_type_cnt = 0; + bool dump_c = false; + __u32 btf_id = -1; + const char *src; + int fd = -1; + int err; + + if (!REQ_ARGS(2)) { + usage(); + return -1; + } + src = GET_ARG(); + + if (is_prefix(src, "map")) { + struct bpf_map_info info = {}; + __u32 len = sizeof(info); + + if (!REQ_ARGS(2)) { + usage(); + return -1; + } + + fd = map_parse_fd_and_info(&argc, &argv, &info, &len); + if (fd < 0) + return -1; + + btf_id = info.btf_id; + if (argc && is_prefix(*argv, "key")) { + root_type_ids[root_type_cnt++] = info.btf_key_type_id; + NEXT_ARG(); + } else if (argc && is_prefix(*argv, "value")) { + root_type_ids[root_type_cnt++] = info.btf_value_type_id; + NEXT_ARG(); + } else if (argc && is_prefix(*argv, "all")) { + NEXT_ARG(); + } else if (argc && is_prefix(*argv, "kv")) { + root_type_ids[root_type_cnt++] = info.btf_key_type_id; + root_type_ids[root_type_cnt++] = info.btf_value_type_id; + NEXT_ARG(); + } else { + root_type_ids[root_type_cnt++] = info.btf_key_type_id; + root_type_ids[root_type_cnt++] = info.btf_value_type_id; + } + } else if (is_prefix(src, "prog")) { + struct bpf_prog_info info = {}; + __u32 len = sizeof(info); + + if (!REQ_ARGS(2)) { + usage(); + return -1; + } + + fd = prog_parse_fd(&argc, &argv); + if (fd < 0) + return -1; + + err = bpf_obj_get_info_by_fd(fd, &info, &len); + if (err) { + p_err("can't get prog info: %s", strerror(errno)); + goto done; + } + + btf_id = info.btf_id; + } else if (is_prefix(src, "id")) { + char *endptr; + + btf_id = strtoul(*argv, &endptr, 0); + if (*endptr) { + p_err("can't parse %s as ID", *argv); + return -1; + } + NEXT_ARG(); + } else if (is_prefix(src, "file")) { + btf = btf__parse(*argv, NULL); + if (IS_ERR(btf)) { + err = -PTR_ERR(btf); + btf = NULL; + p_err("failed to load BTF from %s: %s", + *argv, strerror(err)); + goto done; + } + NEXT_ARG(); + } else { + err = -1; + p_err("unrecognized BTF source specifier: '%s'", src); + goto done; + } + + while (argc) { + if (is_prefix(*argv, "format")) { + NEXT_ARG(); + if (argc < 1) { + p_err("expecting value for 'format' option\n"); + err = -EINVAL; + goto done; + } + if (strcmp(*argv, "c") == 0) { + dump_c = true; + } else if (strcmp(*argv, "raw") == 0) { + dump_c = false; + } else { + p_err("unrecognized format specifier: '%s', possible values: raw, c", + *argv); + err = -EINVAL; + goto done; + } + NEXT_ARG(); + } else { + p_err("unrecognized option: '%s'", *argv); + err = -EINVAL; + goto done; + } + } + + if (!btf) { + err = btf__get_from_id(btf_id, &btf); + if (err) { + p_err("get btf by id (%u): %s", btf_id, strerror(err)); + goto done; + } + if (!btf) { + err = -ENOENT; + p_err("can't find btf with ID (%u)", btf_id); + goto done; + } + } + + if (dump_c) { + if (json_output) { + p_err("JSON output for C-syntax dump is not supported"); + err = -ENOTSUP; + goto done; + } + err = dump_btf_c(btf, root_type_ids, root_type_cnt); + } else { + err = dump_btf_raw(btf, root_type_ids, root_type_cnt); + } + +done: + close(fd); + btf__free(btf); + return err; +} + +static int btf_parse_fd(int *argc, char ***argv) +{ + unsigned int id; + char *endptr; + int fd; + + if (!is_prefix(*argv[0], "id")) { + p_err("expected 'id', got: '%s'?", **argv); + return -1; + } + NEXT_ARGP(); + + id = strtoul(**argv, &endptr, 0); + if (*endptr) { + p_err("can't parse %s as ID", **argv); + return -1; + } + NEXT_ARGP(); + + fd = bpf_btf_get_fd_by_id(id); + if (fd < 0) + p_err("can't get BTF object by id (%u): %s", + id, strerror(errno)); + + return fd; +} + +static void delete_btf_table(struct btf_attach_table *tab) +{ + struct btf_attach_point *obj; + struct hlist_node *tmp; + + unsigned int bkt; + + hash_for_each_safe(tab->table, bkt, tmp, obj, hash) { + hash_del(&obj->hash); + free(obj); + } +} + +static int +build_btf_type_table(struct btf_attach_table *tab, enum bpf_obj_type type, + void *info, __u32 *len) +{ + static const char * const names[] = { + [BPF_OBJ_UNKNOWN] = "unknown", + [BPF_OBJ_PROG] = "prog", + [BPF_OBJ_MAP] = "map", + }; + struct btf_attach_point *obj_node; + __u32 btf_id, id = 0; + int err; + int fd; + + while (true) { + switch (type) { + case BPF_OBJ_PROG: + err = bpf_prog_get_next_id(id, &id); + break; + case BPF_OBJ_MAP: + err = bpf_map_get_next_id(id, &id); + break; + default: + err = -1; + p_err("unexpected object type: %d", type); + goto err_free; + } + if (err) { + if (errno == ENOENT) { + err = 0; + break; + } + p_err("can't get next %s: %s%s", names[type], + strerror(errno), + errno == EINVAL ? " -- kernel too old?" : ""); + goto err_free; + } + + switch (type) { + case BPF_OBJ_PROG: + fd = bpf_prog_get_fd_by_id(id); + break; + case BPF_OBJ_MAP: + fd = bpf_map_get_fd_by_id(id); + break; + default: + err = -1; + p_err("unexpected object type: %d", type); + goto err_free; + } + if (fd < 0) { + if (errno == ENOENT) + continue; + p_err("can't get %s by id (%u): %s", names[type], id, + strerror(errno)); + err = -1; + goto err_free; + } + + memset(info, 0, *len); + err = bpf_obj_get_info_by_fd(fd, info, len); + close(fd); + if (err) { + p_err("can't get %s info: %s", names[type], + strerror(errno)); + goto err_free; + } + + switch (type) { + case BPF_OBJ_PROG: + btf_id = ((struct bpf_prog_info *)info)->btf_id; + break; + case BPF_OBJ_MAP: + btf_id = ((struct bpf_map_info *)info)->btf_id; + break; + default: + err = -1; + p_err("unexpected object type: %d", type); + goto err_free; + } + if (!btf_id) + continue; + + obj_node = calloc(1, sizeof(*obj_node)); + if (!obj_node) { + p_err("failed to allocate memory: %s", strerror(errno)); + err = -ENOMEM; + goto err_free; + } + + obj_node->obj_id = id; + obj_node->btf_id = btf_id; + hash_add(tab->table, &obj_node->hash, obj_node->btf_id); + } + + return 0; + +err_free: + delete_btf_table(tab); + return err; +} + +static int +build_btf_tables(struct btf_attach_table *btf_prog_table, + struct btf_attach_table *btf_map_table) +{ + struct bpf_prog_info prog_info; + __u32 prog_len = sizeof(prog_info); + struct bpf_map_info map_info; + __u32 map_len = sizeof(map_info); + int err = 0; + + err = build_btf_type_table(btf_prog_table, BPF_OBJ_PROG, &prog_info, + &prog_len); + if (err) + return err; + + err = build_btf_type_table(btf_map_table, BPF_OBJ_MAP, &map_info, + &map_len); + if (err) { + delete_btf_table(btf_prog_table); + return err; + } + + return 0; +} + +static void +show_btf_plain(struct bpf_btf_info *info, int fd, + struct btf_attach_table *btf_prog_table, + struct btf_attach_table *btf_map_table) +{ + struct btf_attach_point *obj; + int n; + + printf("%u: ", info->id); + printf("size %uB", info->btf_size); + + n = 0; + hash_for_each_possible(btf_prog_table->table, obj, hash, info->id) { + if (obj->btf_id == info->id) + printf("%s%u", n++ == 0 ? " prog_ids " : ",", + obj->obj_id); + } + + n = 0; + hash_for_each_possible(btf_map_table->table, obj, hash, info->id) { + if (obj->btf_id == info->id) + printf("%s%u", n++ == 0 ? " map_ids " : ",", + obj->obj_id); + } + emit_obj_refs_plain(&refs_table, info->id, "\n\tpids "); + + printf("\n"); +} + +static void +show_btf_json(struct bpf_btf_info *info, int fd, + struct btf_attach_table *btf_prog_table, + struct btf_attach_table *btf_map_table) +{ + struct btf_attach_point *obj; + + jsonw_start_object(json_wtr); /* btf object */ + jsonw_uint_field(json_wtr, "id", info->id); + jsonw_uint_field(json_wtr, "size", info->btf_size); + + jsonw_name(json_wtr, "prog_ids"); + jsonw_start_array(json_wtr); /* prog_ids */ + hash_for_each_possible(btf_prog_table->table, obj, hash, + info->id) { + if (obj->btf_id == info->id) + jsonw_uint(json_wtr, obj->obj_id); + } + jsonw_end_array(json_wtr); /* prog_ids */ + + jsonw_name(json_wtr, "map_ids"); + jsonw_start_array(json_wtr); /* map_ids */ + hash_for_each_possible(btf_map_table->table, obj, hash, + info->id) { + if (obj->btf_id == info->id) + jsonw_uint(json_wtr, obj->obj_id); + } + jsonw_end_array(json_wtr); /* map_ids */ + + emit_obj_refs_json(&refs_table, info->id, json_wtr); /* pids */ + + jsonw_end_object(json_wtr); /* btf object */ +} + +static int +show_btf(int fd, struct btf_attach_table *btf_prog_table, + struct btf_attach_table *btf_map_table) +{ + struct bpf_btf_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 BTF object info: %s", strerror(errno)); + return -1; + } + + if (json_output) + show_btf_json(&info, fd, btf_prog_table, btf_map_table); + else + show_btf_plain(&info, fd, btf_prog_table, btf_map_table); + + return 0; +} + +static int do_show(int argc, char **argv) +{ + struct btf_attach_table btf_prog_table; + struct btf_attach_table btf_map_table; + int err, fd = -1; + __u32 id = 0; + + if (argc == 2) { + fd = btf_parse_fd(&argc, &argv); + if (fd < 0) + return -1; + } + + if (argc) { + if (fd >= 0) + close(fd); + return BAD_ARG(); + } + + hash_init(btf_prog_table.table); + hash_init(btf_map_table.table); + err = build_btf_tables(&btf_prog_table, &btf_map_table); + if (err) { + if (fd >= 0) + close(fd); + return err; + } + build_obj_refs_table(&refs_table, BPF_OBJ_BTF); + + if (fd >= 0) { + err = show_btf(fd, &btf_prog_table, &btf_map_table); + close(fd); + goto exit_free; + } + + if (json_output) + jsonw_start_array(json_wtr); /* root array */ + + while (true) { + err = bpf_btf_get_next_id(id, &id); + if (err) { + if (errno == ENOENT) { + err = 0; + break; + } + p_err("can't get next BTF object: %s%s", + strerror(errno), + errno == EINVAL ? " -- kernel too old?" : ""); + err = -1; + break; + } + + fd = bpf_btf_get_fd_by_id(id); + if (fd < 0) { + if (errno == ENOENT) + continue; + p_err("can't get BTF object by id (%u): %s", + id, strerror(errno)); + err = -1; + break; + } + + err = show_btf(fd, &btf_prog_table, &btf_map_table); + close(fd); + if (err) + break; + } + + if (json_output) + jsonw_end_array(json_wtr); /* root array */ + +exit_free: + delete_btf_table(&btf_prog_table); + delete_btf_table(&btf_map_table); + delete_obj_refs_table(&refs_table); + + return err; +} + +static int do_help(int argc, char **argv) +{ + if (json_output) { + jsonw_null(json_wtr); + return 0; + } + + fprintf(stderr, + "Usage: %1$s %2$s { show | list } [id BTF_ID]\n" + " %1$s %2$s dump BTF_SRC [format FORMAT]\n" + " %1$s %2$s help\n" + "\n" + " BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] | file FILE }\n" + " FORMAT := { raw | c }\n" + " " HELP_SPEC_MAP "\n" + " " HELP_SPEC_PROGRAM "\n" + " " HELP_SPEC_OPTIONS "\n" + "", + bin_name, "btf"); + + return 0; +} + +static const struct cmd cmds[] = { + { "show", do_show }, + { "list", do_show }, + { "help", do_help }, + { "dump", do_dump }, + { 0 } +}; + +int do_btf(int argc, char **argv) +{ + return cmd_select(cmds, argc, argv, do_help); +} |