]> granicus.if.org Git - strace/blobdiff - bpf.c
nlattr: add UID/GID netlink attribute decoders
[strace] / bpf.c
diff --git a/bpf.c b/bpf.c
index 04c677a173adfc348830b2f3311fdcb7ad43b5e8..e5dc4eeb1bd037aeebd96b62e8cb3fc5c78d656a 100644 (file)
--- a/bpf.c
+++ b/bpf.c
@@ -1,5 +1,7 @@
 /*
- * Copyright (c) 2015 Dmitry V. Levin <ldv@altlinux.org>
+ * Copyright (c) 2015-2017 Dmitry V. Levin <ldv@altlinux.org>
+ * Copyright (c) 2017 Quentin Monnet <quentin.monnet@6wind.com>
+ * Copyright (c) 2015-2018 The strace developers.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 
 #include "defs.h"
+#include "print_fields.h"
 
 #ifdef HAVE_LINUX_BPF_H
 # include <linux/bpf.h>
 #endif
+#include <linux/filter.h>
+
+#include "bpf_attr.h"
 
 #include "xlat/bpf_commands.h"
+#include "xlat/bpf_file_mode_flags.h"
 #include "xlat/bpf_map_types.h"
+#include "xlat/bpf_map_flags.h"
 #include "xlat/bpf_prog_types.h"
+#include "xlat/bpf_prog_flags.h"
 #include "xlat/bpf_map_update_elem_flags.h"
+#include "xlat/bpf_attach_type.h"
+#include "xlat/bpf_attach_flags.h"
+#include "xlat/bpf_query_flags.h"
+#include "xlat/ebpf_regs.h"
+#include "xlat/numa_node.h"
+
+#define DECL_BPF_CMD_DECODER(bpf_cmd_decoder)                          \
+int                                                                    \
+bpf_cmd_decoder(struct tcb *const tcp,                                 \
+               const kernel_ulong_t addr,                              \
+               const unsigned int size,                                \
+               void *const data)                                       \
+/* End of DECL_BPF_CMD_DECODER definition. */
+
+#define BEGIN_BPF_CMD_DECODER(bpf_cmd)                                 \
+       static DECL_BPF_CMD_DECODER(decode_ ## bpf_cmd)                 \
+       {                                                               \
+               struct bpf_cmd ## _struct attr = {};                    \
+               const size_t attr_size = bpf_cmd ## _struct_size;       \
+               const unsigned int len = MIN(size, attr_size);          \
+               memcpy(&attr, data, len);                               \
+               do {                                                    \
+/* End of BEGIN_BPF_CMD_DECODER definition. */
+
+#define END_BPF_CMD_DECODER(rval)                                      \
+                       decode_attr_extra_data(tcp, data, size, attr_size); \
+               } while (0);                                            \
+               tprints("}");                                           \
+               return (rval);                                          \
+       }                                                               \
+/* End of END_BPF_CMD_DECODER definition. */
+
+#define BPF_CMD_ENTRY(bpf_cmd)                                         \
+       [bpf_cmd] = decode_ ## bpf_cmd
+
+typedef DECL_BPF_CMD_DECODER((*bpf_cmd_decoder_t));
+
+/*
+ * A note about bpf syscall decoder: it doesn't perform any size sanity checks,
+ * so even if it leads to partial copying of one of the fields, the command
+ * handler will still use the (partially-copied-from-userspace, partially
+ * zeroed) field value.  That's why we stop decoding and check for known sizes
+ * that correspond to released versions of the structure used by the specific
+ * command - it looks like the most sensible way to parse this insanity.
+ */
 
 static int
-bpf_map_create(struct tcb *const tcp, const kernel_ulong_t addr,
-              unsigned int size)
+decode_attr_extra_data(struct tcb *const tcp,
+                      const char *data,
+                      unsigned int size,
+                      const size_t attr_size)
 {
-       struct {
-               uint32_t map_type, key_size, value_size, max_entries;
-       } attr = {};
+       if (size <= attr_size)
+               return 0;
+
+       data += attr_size;
+       size -= attr_size;
+
+       unsigned int i;
+       for (i = 0; i < size; ++i) {
+               if (data[i]) {
+                       tprints(", ");
+                       if (abbrev(tcp)) {
+                               tprints("...");
+                       } else {
+                               tprintf("/* bytes %zu..%zu */ ",
+                                       attr_size, attr_size + size - 1);
+                               print_quoted_string(data, size,
+                                                   QUOTE_FORCE_HEX);
+                       }
+                       return RVAL_DECODED;
+               }
+       }
+
+       return 0;
+}
+
+struct ebpf_insn {
+       uint8_t code;
+       uint8_t dst_reg:4;
+       uint8_t src_reg:4;
+       int16_t off;
+       int32_t imm;
+};
+
+struct ebpf_insns_data {
+       unsigned int count;
+};
+
+static bool
+print_ebpf_insn(struct tcb * const tcp, void * const elem_buf,
+               const size_t elem_size, void * const data)
+{
+       struct ebpf_insns_data *eid = data;
+       struct ebpf_insn *insn = elem_buf;
+
+       if (eid->count++ >= BPF_MAXINSNS) {
+               tprints("...");
+               return false;
+       }
+
+       tprints("{code=");
+       print_bpf_filter_code(insn->code, true);
+
+       /* We can't use PRINT_FIELD_XVAL on bit fields */
+       tprints(", dst_reg=");
+       printxval_index(ebpf_regs, insn->dst_reg, "BPF_REG_???");
+       tprints(", src_reg=");
+       printxval_index(ebpf_regs, insn->src_reg, "BPF_REG_???");
+
+       PRINT_FIELD_D(", ", *insn, off);
+       PRINT_FIELD_X(", ", *insn, imm);
+       tprints("}");
 
-       if (!size) {
+       return true;
+}
+
+static void
+print_ebpf_prog(struct tcb *const tcp, const uint64_t addr, const uint32_t len)
+{
+       print_big_u64_addr(addr);
+       if (abbrev(tcp)) {
                printaddr(addr);
-               return RVAL_DECODED | RVAL_FD;
+       } else {
+               struct ebpf_insns_data eid = {};
+               struct ebpf_insn insn;
+
+               print_array(tcp, addr, len, &insn, sizeof(insn),
+                           tfetch_mem, print_ebpf_insn, &eid);
+       }
+}
+
+BEGIN_BPF_CMD_DECODER(BPF_MAP_CREATE)
+{
+       PRINT_FIELD_XVAL_INDEX("{", attr, map_type, bpf_map_types,
+                              "BPF_MAP_TYPE_???");
+       PRINT_FIELD_U(", ", attr, key_size);
+       PRINT_FIELD_U(", ", attr, value_size);
+       PRINT_FIELD_U(", ", attr, max_entries);
+
+       /* map_flags field was added in Linux commit v4.6-rc1~91^2~108^2~6. */
+       if (len <= offsetof(struct BPF_MAP_CREATE_struct, map_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, map_flags, bpf_map_flags, "BPF_F_???");
+
+       /*
+        * inner_map_fd field was added in Linux commit
+        * v4.12-rc1~64^3~373^2~2.
+        */
+       if (len <= offsetof(struct BPF_MAP_CREATE_struct, inner_map_fd))
+               break;
+       PRINT_FIELD_FD(", ", attr, inner_map_fd, tcp);
+
+       /* numa_node field was added in Linux commit v4.14-rc1~130^2~196^2~1. */
+       if (len <= offsetof(struct BPF_MAP_CREATE_struct, numa_node))
+               break;
+       if (attr.map_flags & BPF_F_NUMA_NODE) {
+               /*
+                * Kernel uses the value of -1 as a designation for "no NUMA
+                * node specified", and even uses NUMA_NO_NODE constant;
+                * however, the constant definition is not a part of UAPI
+                * headers, thus we can't simply print this named constant
+                * instead of the value. Let's force verbose xlat style instead
+                * in order to provide the information for the user while
+                * not hampering the availability to derive the actual value
+                * without the access to the kernel headers.
+                */
+               tprints(", numa_node=");
+               printxvals_ex(attr.numa_node, NULL,
+                             XLAT_STYLE_FMT_U | XLAT_STYLE_VERBOSE,
+                             numa_node, NULL);
        }
-       if (size > sizeof(attr))
-               size = sizeof(attr);
-       if (umoven_or_printaddr(tcp, addr, size, &attr))
-               return RVAL_DECODED | RVAL_FD;
 
-       tprints("{map_type=");
-       printxval(bpf_map_types, attr.map_type, "BPF_MAP_TYPE_???");
-       tprintf(", key_size=%u, value_size=%u, max_entries=%u}",
-               attr.key_size, attr.value_size, attr.max_entries);
+       /* map_name field was added in Linux commit v4.15-rc1~84^2~605^2~3. */
+       if (len <= offsetof(struct BPF_MAP_CREATE_struct, map_name))
+               break;
+       PRINT_FIELD_CSTRING_SZ(", ", attr, map_name,
+                              MIN(sizeof(attr.map_name),
+                                  len - offsetof(struct BPF_MAP_CREATE_struct,
+                                                 map_name)));
+
+       /*
+        * map_ifindex field was added in Linux commit
+        * v4.16-rc1~123^2~145^2~5^2~8.
+        */
+       if (len <= offsetof(struct BPF_MAP_CREATE_struct, map_ifindex))
+               break;
+       PRINT_FIELD_IFINDEX(", ", attr, map_ifindex);
+}
+END_BPF_CMD_DECODER(RVAL_DECODED | RVAL_FD)
+
+BEGIN_BPF_CMD_DECODER(BPF_MAP_LOOKUP_ELEM)
+{
+       PRINT_FIELD_FD("{", attr, map_fd, tcp);
+       PRINT_FIELD_ADDR64(", ", attr, key);
+       PRINT_FIELD_ADDR64(", ", attr, value);
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_MAP_UPDATE_ELEM)
+{
+       PRINT_FIELD_FD("{", attr, map_fd, tcp);
+       PRINT_FIELD_ADDR64(", ", attr, key);
+       PRINT_FIELD_ADDR64(", ", attr, value);
+       PRINT_FIELD_XVAL_INDEX(", ", attr, flags, bpf_map_update_elem_flags,
+                              "BPF_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_MAP_DELETE_ELEM)
+{
+       PRINT_FIELD_FD("{", attr, map_fd, tcp);
+       PRINT_FIELD_ADDR64(", ", attr, key);
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
 
-       return RVAL_DECODED | RVAL_FD;
+BEGIN_BPF_CMD_DECODER(BPF_MAP_GET_NEXT_KEY)
+{
+       PRINT_FIELD_FD("{", attr, map_fd, tcp);
+       PRINT_FIELD_ADDR64(", ", attr, key);
+       PRINT_FIELD_ADDR64(", ", attr, next_key);
 }
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_LOAD)
+{
+       PRINT_FIELD_XVAL_INDEX("{", attr, prog_type, bpf_prog_types,
+                              "BPF_PROG_TYPE_???");
+       PRINT_FIELD_U(", ", attr, insn_cnt);
+       tprints(", insns=");
+       print_ebpf_prog(tcp, attr.insns, attr.insn_cnt);
+
+       tprintf(", license=");
+       print_big_u64_addr(attr.license);
+       printstr(tcp, attr.license);
+
+       /* log_* fields were added in Linux commit v3.18-rc1~52^2~1^2~4.  */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, log_level))
+               break;
+       PRINT_FIELD_U(", ", attr, log_level);
+       PRINT_FIELD_U(", ", attr, log_size);
+       tprintf(", log_buf=");
+       print_big_u64_addr(attr.log_buf);
+       printstr_ex(tcp, attr.log_buf, attr.log_size, QUOTE_0_TERMINATED);
+
+       /* kern_version field was added in Linux commit v4.1-rc1~84^2~50.  */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, kern_version))
+               break;
+       tprintf(", kern_version=KERNEL_VERSION(%u, %u, %u)",
+               attr.kern_version >> 16,
+               (attr.kern_version >> 8) & 0xFF,
+               attr.kern_version & 0xFF);
+
+       /* prog_flags field was added in Linux commit v4.12-rc2~34^2~29^2~2.  */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, prog_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, prog_flags, bpf_prog_flags, "BPF_F_???");
+
+       /* prog_name field was added in Linux commit v4.15-rc1~84^2~605^2~4. */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, prog_name))
+               break;
+       PRINT_FIELD_CSTRING_SZ(", ", attr, prog_name,
+                              MIN(sizeof(attr.prog_name),
+                                  len - offsetof(struct BPF_PROG_LOAD_struct,
+                                                  prog_name)));
+
+       /*
+        * prog_ifindex field was added as prog_target_ifindex in Linux commit
+        * v4.15-rc1~84^2~127^2~13 and renamed to its current name in
+        * v4.15-rc1~15^2~5^2~3^2~7.
+        */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, prog_ifindex))
+               break;
+       PRINT_FIELD_IFINDEX(", ", attr, prog_ifindex);
+
+       /*
+        * expected_attach_type was added in Linux commit
+        * v4.17-rc1~148^2~19^2^2~8.
+        */
+       if (len <= offsetof(struct BPF_PROG_LOAD_struct, expected_attach_type))
+               break;
+       PRINT_FIELD_XVAL(", ", attr, expected_attach_type, bpf_attach_type,
+                        "BPF_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED | RVAL_FD)
+
+BEGIN_BPF_CMD_DECODER(BPF_OBJ_PIN)
+{
+       tprintf("{pathname=");
+       print_big_u64_addr(attr.pathname);
+       printpath(tcp, attr.pathname);
+
+       PRINT_FIELD_FD(", ", attr, bpf_fd, tcp);
+
+       /* file_flags field was added in Linux v4.15-rc1~84^2~384^2~4 */
+       if (len <= offsetof(struct BPF_OBJ_PIN_struct, file_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, file_flags, bpf_file_mode_flags,
+                         "BPF_F_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED | RVAL_FD)
+
+#define decode_BPF_OBJ_GET decode_BPF_OBJ_PIN
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_ATTACH)
+{
+       PRINT_FIELD_FD("{", attr, target_fd, tcp);
+       PRINT_FIELD_FD(", ", attr, attach_bpf_fd, tcp);
+       PRINT_FIELD_XVAL_INDEX(", ", attr, attach_type, bpf_attach_type,
+                              "BPF_???");
+       PRINT_FIELD_FLAGS(", ", attr, attach_flags, bpf_attach_flags,
+                         "BPF_F_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_DETACH)
+{
+       PRINT_FIELD_FD("{", attr, target_fd, tcp);
+       PRINT_FIELD_XVAL_INDEX(", ", attr, attach_type, bpf_attach_type,
+                              "BPF_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_TEST_RUN)
+{
+       PRINT_FIELD_FD("{test={", attr, prog_fd, tcp);
+       PRINT_FIELD_U(", ", attr, retval);
+       PRINT_FIELD_U(", ", attr, data_size_in);
+       PRINT_FIELD_U(", ", attr, data_size_out);
+       PRINT_FIELD_ADDR64(", ", attr, data_in);
+       PRINT_FIELD_ADDR64(", ", attr, data_out);
+       PRINT_FIELD_U(", ", attr, repeat);
+       PRINT_FIELD_U(", ", attr, duration);
+       tprints("}");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_GET_NEXT_ID)
+{
+       PRINT_FIELD_U("{", attr, start_id);
+       PRINT_FIELD_U(", ", attr, next_id);
+
+       /* open_flags field has been added in Linux v4.15-rc1~84^2~384^2~4 */
+       if (len <= offsetof(struct BPF_PROG_GET_NEXT_ID_struct, open_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, open_flags, bpf_file_mode_flags,
+                         "BPF_F_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+#define decode_BPF_MAP_GET_NEXT_ID decode_BPF_PROG_GET_NEXT_ID
+
+BEGIN_BPF_CMD_DECODER(BPF_PROG_GET_FD_BY_ID)
+{
+       PRINT_FIELD_U("{", attr, prog_id);
+       PRINT_FIELD_U(", ", attr, next_id);
+
+       /* open_flags field has been added in Linux v4.15-rc1~84^2~384^2~4 */
+       if (len <= offsetof(struct BPF_PROG_GET_FD_BY_ID_struct, open_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, open_flags, bpf_file_mode_flags,
+                         "BPF_F_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_MAP_GET_FD_BY_ID)
+{
+       PRINT_FIELD_U("{", attr, map_id);
+       PRINT_FIELD_U(", ", attr, next_id);
+
+       /* open_flags field has been added in Linux v4.15-rc1~84^2~384^2~4 */
+       if (len <= offsetof(struct BPF_MAP_GET_FD_BY_ID_struct, open_flags))
+               break;
+       PRINT_FIELD_FLAGS(", ", attr, open_flags, bpf_file_mode_flags,
+                         "BPF_F_???");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+struct obj_get_info_saved;
+typedef void (*print_bpf_obj_info_fn)(struct tcb *,
+                                     uint32_t bpf_fd,
+                                     const char *info_buf,
+                                     uint32_t size,
+                                     struct obj_get_info_saved *saved);
+
+struct obj_get_info_saved {
+       print_bpf_obj_info_fn print_fn;
+
+       uint32_t info_len;
+
+       uint32_t jited_prog_len;
+       uint32_t xlated_prog_len;
+       uint32_t nr_map_ids;
+};
 
 static void
-bpf_map_update_elem(struct tcb *const tcp, const kernel_ulong_t addr,
-                   unsigned int size)
+print_bpf_map_info(struct tcb * const tcp, uint32_t bpf_fd,
+                  const char *info_buf, uint32_t size,
+                  struct obj_get_info_saved *saved)
 {
-       struct {
-               uint32_t map_fd;
-               uint64_t ATTRIBUTE_ALIGNED(8) key;
-               uint64_t ATTRIBUTE_ALIGNED(8) value;
-               uint64_t flags;
-       } attr = {};
+       if (entering(tcp))
+               return;
+
+       struct bpf_map_info_struct info = { 0 };
+       const unsigned int len = MIN(size, bpf_map_info_struct_size);
+
+       memcpy(&info, info_buf, len);
+
+       PRINT_FIELD_XVAL("{", info, type, bpf_map_types, "BPF_MAP_TYPE_???");
+       PRINT_FIELD_U(", ", info, id);
+       PRINT_FIELD_U(", ", info, key_size);
+       PRINT_FIELD_U(", ", info, value_size);
+       PRINT_FIELD_U(", ", info, max_entries);
+       PRINT_FIELD_FLAGS(", ", info, map_flags, bpf_map_flags, "BPF_F_???");
+
+       /*
+        * "name" field was introduced by Linux commit v4.15-rc1~84^2~605^2~3.
+        */
+       if (len <= offsetof(struct bpf_map_info_struct, name))
+               goto print_bpf_map_info_end;
+       PRINT_FIELD_CSTRING(", ", info, name);
+
+       /*
+        * ifindex, netns_dev, and netns_ino fields were introduced
+        * by Linux commit v4.16-rc1~123^2~109^2~5^2~4.
+        */
+       if (len <= offsetof(struct bpf_map_info_struct, ifindex))
+               goto print_bpf_map_info_end;
+       PRINT_FIELD_IFINDEX(", ", info, ifindex);
+       PRINT_FIELD_DEV(", ", info, netns_dev);
+       PRINT_FIELD_U(", ", info, netns_ino);
+
+       decode_attr_extra_data(tcp, info_buf, size, bpf_map_info_struct_size);
+
+print_bpf_map_info_end:
+       tprints("}");
+}
+
+static void
+print_bpf_prog_info(struct tcb * const tcp, uint32_t bpf_fd,
+                   const char *info_buf, uint32_t size,
+                   struct obj_get_info_saved *saved)
+{
+       struct bpf_prog_info_struct info = { 0 };
+       const unsigned int len = MIN(size, bpf_prog_info_struct_size);
+       uint64_t map_id_buf;
+
+       memcpy(&info, info_buf, len);
+
+       if (entering(tcp)) {
+               saved->jited_prog_len = info.jited_prog_len;
+               saved->xlated_prog_len = info.xlated_prog_len;
+               saved->nr_map_ids = info.nr_map_ids;
 
-       if (!size) {
-               printaddr(addr);
                return;
        }
-       if (size > sizeof(attr))
-               size = sizeof(attr);
-       if (umoven_or_printaddr(tcp, addr, size, &attr))
-               return;
 
-       tprints("{map_fd=");
-       printfd(tcp, attr.map_fd);
-       tprintf(", key=%#" PRIx64 ", value=%#" PRIx64 ", flags=",
-               attr.key, attr.value);
-       printxval64(bpf_map_update_elem_flags, attr.flags, "BPF_???");
+       PRINT_FIELD_XVAL("{", info, type, bpf_prog_types, "BPF_PROG_TYPE_???");
+       PRINT_FIELD_U(", ", info, id);
+       PRINT_FIELD_HEX_ARRAY(", ", info, tag);
+
+       tprints(", jited_prog_len=");
+       if (saved->jited_prog_len != info.jited_prog_len)
+               tprintf("%" PRIu32 " => ", saved->jited_prog_len);
+       tprintf("%" PRIu32, info.jited_prog_len);
+
+       tprints(", jited_prog_insns=");
+       print_big_u64_addr(info.jited_prog_insns);
+       printstr_ex(tcp, info.jited_prog_insns, info.jited_prog_len,
+                   QUOTE_FORCE_HEX);
+
+       tprints(", xlated_prog_len=");
+       if (saved->xlated_prog_len != info.xlated_prog_len)
+               tprintf("%" PRIu32 " => ", saved->xlated_prog_len);
+       tprintf("%" PRIu32, info.xlated_prog_len);
+
+       tprints(", xlated_prog_insns=");
+       print_ebpf_prog(tcp, info.xlated_prog_insns,
+                       MIN(saved->xlated_prog_len, info.xlated_prog_len) / 8);
+
+       /*
+        * load_time, created_by_uid, nr_map_ids, map_ids, and name fields
+        * were introduced by Linux commit v4.15-rc1~84^2~605^2~4.
+        */
+       if (len <= offsetof(struct bpf_prog_info_struct, load_time))
+               goto print_bpf_prog_info_end;
+       PRINT_FIELD_U(", ", info, load_time);
+       PRINT_FIELD_UID(", ", info, created_by_uid);
+
+       tprints(", nr_map_ids=");
+       if (saved->nr_map_ids != info.nr_map_ids)
+               tprintf("%" PRIu32 " => ", saved->nr_map_ids);
+       tprintf("%" PRIu32, info.nr_map_ids);
+
+       tprints(", map_ids=");
+       print_big_u64_addr(info.map_ids);
+       print_array(tcp, info.map_ids, MIN(saved->nr_map_ids, info.nr_map_ids),
+                   &map_id_buf, sizeof(map_id_buf),
+                   tfetch_mem, print_uint32_array_member, 0);
+
+       PRINT_FIELD_CSTRING(", ", info, name);
+
+       /*
+        * ifindex, netns_dev, and netns_ino fields were introduced
+        * by Linux commit v4.16-rc1~123^2~227^2~5^2~2.
+        */
+       if (len <= offsetof(struct bpf_prog_info_struct, ifindex))
+               goto print_bpf_prog_info_end;
+       PRINT_FIELD_IFINDEX(", ", info, ifindex);
+       PRINT_FIELD_DEV(", ", info, netns_dev);
+       PRINT_FIELD_U(", ", info, netns_ino);
+
+       decode_attr_extra_data(tcp, info_buf, size, bpf_prog_info_struct_size);
+
+print_bpf_prog_info_end:
        tprints("}");
 }
 
+static const char *
+fetch_bpf_obj_info(struct tcb * const tcp, uint64_t info, uint32_t size)
+{
+       static char *info_buf;
+
+       if (!info_buf)
+               info_buf = xmalloc(get_pagesize());
+
+       memset(info_buf, 0, get_pagesize());
+
+       if (size > 0 && size <= get_pagesize()
+           && !umoven(tcp, info, size, info_buf))
+               return info_buf;
+
+       return NULL;
+}
+
 static void
-bpf_map_delete_elem(struct tcb *const tcp, const kernel_ulong_t addr,
-                   unsigned int size)
+print_bpf_obj_info_addr(struct tcb * const tcp, uint64_t addr)
 {
-       struct {
-               uint32_t map_fd;
-               uint64_t ATTRIBUTE_ALIGNED(8) key;
-       } attr = {};
+       if (exiting(tcp))
+               printaddr64(addr);
+}
 
-       if (!size) {
-               printaddr(addr);
+static void
+print_bpf_obj_info(struct tcb * const tcp, uint32_t bpf_fd, uint64_t info,
+                  uint32_t size, struct obj_get_info_saved *saved)
+{
+       if (abbrev(tcp)) {
+               print_bpf_obj_info_addr(tcp, info);
                return;
        }
-       if (size > sizeof(attr))
-               size = sizeof(attr);
-       if (umoven_or_printaddr(tcp, addr, size, &attr))
+
+       static struct {
+               const char *id;
+               print_bpf_obj_info_fn print_fn;
+       } obj_printers[] = {
+               { "anon_inode:bpf-map", print_bpf_map_info },
+               { "anon_inode:bpf-prog", print_bpf_prog_info }
+       };
+
+       if (entering(tcp)) {
+               char path[PATH_MAX + 1];
+
+               if (getfdpath(tcp, bpf_fd, path, sizeof(path)) > 0) {
+                       for (size_t i = 0; i < ARRAY_SIZE(obj_printers); ++i) {
+                               if (!strcmp(path, obj_printers[i].id)) {
+                                       saved->print_fn =
+                                               obj_printers[i].print_fn;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if (!saved || !saved->print_fn) {
+               print_bpf_obj_info_addr(tcp, info);
                return;
+       }
+
+       const char *info_buf = fetch_bpf_obj_info(tcp, info, size);
 
-       tprints("{map_fd=");
-       printfd(tcp, attr.map_fd);
-       tprintf(", key=%#" PRIx64 "}", attr.key);
+       if (info_buf)
+               saved->print_fn(tcp, bpf_fd, info_buf, size, saved);
+       else
+               print_bpf_obj_info_addr(tcp, info);
 }
 
-static int
-bpf_map_io(struct tcb *const tcp, const kernel_ulong_t addr, unsigned int size,
-          const char *const text)
-{
-       struct bpf_io_elem_struct {
-               uint32_t map_fd;
-               uint64_t ATTRIBUTE_ALIGNED(8) key;
-               uint64_t ATTRIBUTE_ALIGNED(8) value;
-       } attr = {};
-
-       if (exiting(tcp)) {
-               if (!syserror(tcp) && !umove_or_printaddr(tcp, addr, &attr))
-                       tprintf(", %s=%#" PRIx64, text, attr.value);
-               tprints("}");
-               return RVAL_DECODED;
-       }
+BEGIN_BPF_CMD_DECODER(BPF_OBJ_GET_INFO_BY_FD)
+{
+       struct obj_get_info_saved *saved;
 
-       if (!size) {
-               printaddr(addr);
-               return RVAL_DECODED;
+       if (entering(tcp)) {
+               saved = xcalloc(1, sizeof(*saved));
+               saved->info_len = attr.info_len;
+               set_tcb_priv_data(tcp, saved, free);
+
+               PRINT_FIELD_FD("{info={", attr, bpf_fd, tcp);
+               PRINT_FIELD_U(", ", attr, info_len);
+       } else {
+               saved = get_tcb_priv_data(tcp);
+
+               if (saved && (saved->info_len != attr.info_len))
+                       tprintf(" => %u", attr.info_len);
+
+               tprintf(", info=");
        }
-       if (size > sizeof(attr))
-               size = sizeof(attr);
-       if (umoven_or_printaddr(tcp, addr, size, &attr))
-               return RVAL_DECODED;
 
-       tprints("{map_fd=");
-       printfd(tcp, attr.map_fd);
-       tprintf(", key=%#" PRIx64, attr.key);
+       print_bpf_obj_info(tcp, attr.bpf_fd, attr.info, attr.info_len, saved);
 
-       return 0;
+       if (entering(tcp))
+               return 0;
+
+       tprints("}");
 }
+END_BPF_CMD_DECODER(RVAL_DECODED)
 
-static int
-bpf_prog_load(struct tcb *const tcp, const kernel_ulong_t addr,
-             unsigned int size)
-{
-       struct {
-               uint32_t prog_type, insn_cnt;
-               uint64_t ATTRIBUTE_ALIGNED(8) insns, license;
-               uint32_t log_level, log_size;
-               uint64_t ATTRIBUTE_ALIGNED(8) log_buf;
-               uint32_t kern_version;
-       } attr = {};
-
-       if (!size) {
-               printaddr(addr);
-               return RVAL_DECODED | RVAL_FD;
+BEGIN_BPF_CMD_DECODER(BPF_PROG_QUERY)
+{
+       uint32_t prog_id_buf;
+
+       if (entering(tcp)) {
+               PRINT_FIELD_FD("{query={", attr, target_fd, tcp);
+               PRINT_FIELD_XVAL_INDEX(", ", attr, attach_type, bpf_attach_type,
+                                      "BPF_???");
+               PRINT_FIELD_FLAGS(", ", attr, query_flags, bpf_query_flags,
+                                 "BPF_F_QUERY_???");
+               PRINT_FIELD_FLAGS(", ", attr, attach_flags, bpf_attach_flags,
+                                 "BPF_F_???");
+
+               tprints(", prog_ids=");
+
+               set_tcb_priv_ulong(tcp, attr.prog_cnt);
+
+               return 0;
        }
-       if (size > sizeof(attr))
-               size = sizeof(attr);
-       if (umoven_or_printaddr(tcp, addr, size, &attr))
-               return RVAL_DECODED | RVAL_FD;
-
-       tprints("{prog_type=");
-       printxval(bpf_prog_types, attr.prog_type, "BPF_PROG_TYPE_???");
-       tprintf(", insn_cnt=%u, insns=%#" PRIx64 ", license=",
-               attr.insn_cnt, attr.insns);
-       printstr(tcp, attr.license);
-       tprintf(", log_level=%u, log_size=%u, log_buf=%#" PRIx64 ", kern_version=%u}",
-               attr.log_level, attr.log_size, attr.log_buf, attr.kern_version);
 
-       return RVAL_DECODED | RVAL_FD;
+       print_big_u64_addr(attr.prog_ids);
+       print_array(tcp, attr.prog_ids, attr.prog_cnt, &prog_id_buf,
+                   sizeof(prog_id_buf), tfetch_mem,
+                   print_uint32_array_member, 0);
+
+       tprints(", prog_cnt=");
+       const uint32_t prog_cnt_entering = get_tcb_priv_ulong(tcp);
+       if (prog_cnt_entering != attr.prog_cnt)
+               tprintf("%" PRIu32 " => ", prog_cnt_entering);
+       tprintf("%" PRIu32, attr.prog_cnt);
+       tprints("}");
+}
+END_BPF_CMD_DECODER(RVAL_DECODED)
+
+BEGIN_BPF_CMD_DECODER(BPF_RAW_TRACEPOINT_OPEN)
+{
+       enum { TP_NAME_SIZE = 128 };
+
+       tprintf("{raw_tracepoint={name=");
+       print_big_u64_addr(attr.name);
+       printstr_ex(tcp, attr.name, TP_NAME_SIZE, QUOTE_0_TERMINATED);
+
+       PRINT_FIELD_FD(", ", attr, prog_fd, tcp);
+
+       tprints("}");
 }
+END_BPF_CMD_DECODER(RVAL_DECODED)
 
 SYS_FUNC(bpf)
 {
+       static const bpf_cmd_decoder_t bpf_cmd_decoders[] = {
+               BPF_CMD_ENTRY(BPF_MAP_CREATE),
+               BPF_CMD_ENTRY(BPF_MAP_LOOKUP_ELEM),
+               BPF_CMD_ENTRY(BPF_MAP_UPDATE_ELEM),
+               BPF_CMD_ENTRY(BPF_MAP_DELETE_ELEM),
+               BPF_CMD_ENTRY(BPF_MAP_GET_NEXT_KEY),
+               BPF_CMD_ENTRY(BPF_PROG_LOAD),
+               BPF_CMD_ENTRY(BPF_OBJ_PIN),
+               BPF_CMD_ENTRY(BPF_OBJ_GET),
+               BPF_CMD_ENTRY(BPF_PROG_ATTACH),
+               BPF_CMD_ENTRY(BPF_PROG_DETACH),
+               BPF_CMD_ENTRY(BPF_PROG_TEST_RUN),
+               BPF_CMD_ENTRY(BPF_PROG_GET_NEXT_ID),
+               BPF_CMD_ENTRY(BPF_MAP_GET_NEXT_ID),
+               BPF_CMD_ENTRY(BPF_PROG_GET_FD_BY_ID),
+               BPF_CMD_ENTRY(BPF_MAP_GET_FD_BY_ID),
+               BPF_CMD_ENTRY(BPF_OBJ_GET_INFO_BY_FD),
+               BPF_CMD_ENTRY(BPF_PROG_QUERY),
+               BPF_CMD_ENTRY(BPF_RAW_TRACEPOINT_OPEN),
+       };
+
        const unsigned int cmd = tcp->u_arg[0];
        const kernel_ulong_t addr = tcp->u_arg[1];
        const unsigned int size = tcp->u_arg[2];
        int rc = RVAL_DECODED;
 
        if (entering(tcp)) {
-               printxval(bpf_commands, cmd, "BPF_???");
+               printxval_index(bpf_commands, cmd, "BPF_???");
                tprints(", ");
        }
 
-       switch (cmd) {
-       case BPF_MAP_CREATE:
-               rc = bpf_map_create(tcp, addr, size);
-               break;
-       case BPF_MAP_LOOKUP_ELEM:
-               rc = bpf_map_io(tcp, addr, size, "value");
-               break;
-       case BPF_MAP_UPDATE_ELEM:
-               bpf_map_update_elem(tcp, addr, size);
-               break;
-       case BPF_MAP_DELETE_ELEM:
-               bpf_map_delete_elem(tcp, addr, size);
-               break;
-       case BPF_MAP_GET_NEXT_KEY:
-               rc = bpf_map_io(tcp, addr, size, "next_key");
-               break;
-       case BPF_PROG_LOAD:
-               rc = bpf_prog_load(tcp, addr, size);
-               break;
-       default:
+       if (size > 0
+           && size <= get_pagesize()
+           && cmd < ARRAY_SIZE(bpf_cmd_decoders)
+           && bpf_cmd_decoders[cmd]) {
+               static char *buf;
+
+               if (!buf)
+                       buf = xmalloc(get_pagesize());
+
+               if (!umoven_or_printaddr_ignore_syserror(tcp, addr, size, buf))
+                       rc = bpf_cmd_decoders[cmd](tcp, addr, size, buf);
+       } else {
                printaddr(addr);
-               break;
        }
 
-       if (rc & RVAL_DECODED)
+       if (exiting(tcp) || (rc & RVAL_DECODED))
                tprintf(", %u", size);
 
        return rc;