bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH bpf-next] bpf: Add drgn script to list progs/maps
@ 2020-02-27  2:32 Andrey Ignatov
  2020-02-27  5:45 ` Song Liu
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Andrey Ignatov @ 2020-02-27  2:32 UTC (permalink / raw)
  To: bpf; +Cc: Andrey Ignatov, ast, daniel, osandov, kernel-team

drgn is a debugger that reads kernel memory and uses DWARF to get types
and symbols. See [1], [2] and [3] for more details on drgn.

Since drgn operates on kernel memory it has access to kernel internals
that user space doesn't. It allows to get extended info about various
kernel data structures.

Introduce bpf.py drgn script to list BPF programs and maps and their
properties unavailable to user space via kernel API.

The main use-case bpf.py covers is to show BPF programs attached to
other BPF programs via freplace/fentry/fexit mechanisms introduced
recently. There is no user-space API to get this info and e.g. bpftool
can only show all BPF programs but can't show if program A replaces a
function in program B.

Example:

  % sudo tools/bpf/bpf.py p | grep test_pkt_access
     650: BPF_PROG_TYPE_SCHED_CLS          test_pkt_access
     654: BPF_PROG_TYPE_TRACING            test_main                        linked:[650->25: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access()]
     655: BPF_PROG_TYPE_TRACING            test_subprog1                    linked:[650->29: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog1()]
     656: BPF_PROG_TYPE_TRACING            test_subprog2                    linked:[650->31: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog2()]
     657: BPF_PROG_TYPE_TRACING            test_subprog3                    linked:[650->21: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog3()]
     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
     659: BPF_PROG_TYPE_EXT                new_get_skb_ifindex              linked:[650->23: BPF_TRAMP_REPLACE test_pkt_access->get_skb_ifindex()]
     660: BPF_PROG_TYPE_EXT                new_get_constant                 linked:[650->19: BPF_TRAMP_REPLACE test_pkt_access->get_constant()]

It can be seen that there is a program test_pkt_access, id 650 and there
are multiple other tracing and ext programs attached to functions in
test_pkt_access.

For example the line:

     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]

means that BPF program new_get_skb_len, id 658, type BPF_PROG_TYPE_EXT
replaces (BPF_TRAMP_REPLACE) function get_skb_len() that has BTF id 16
in BPF program test_pkt_access, prog id 650.

Just very simple output is supported now but it can be extended in the
future if needed.

The script is extendable and currently implements two subcommands:
* prog (alias: p) to list all BPF programs;
* map (alias: m) to list all BPF maps;

Developer can simply tweak the script to print interesting pieces of programs
or maps.

The name bpf.py is not super authentic. I'm open to better options.

The script can be sent to drgn repo where it's easier to maintain its
"drgn-ness", but in kernel tree it should be easier to maintain BPF
functionality itself what can be more important in this case.

The script depends on drgn revision [4] where BPF helpers were added.

More examples of output:

  % sudo tools/bpf/bpf.py p | shuf -n 3
      81: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
      94: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
      43: BPF_PROG_TYPE_KPROBE             kprobe__tcp_reno_cong_avoid

  % sudo tools/bpf/bpf.py m | shuf -n 3
     213: BPF_MAP_TYPE_HASH                errors
      30: BPF_MAP_TYPE_ARRAY               sslwall_setting
      41: BPF_MAP_TYPE_LRU_HASH            flow_to_snd

Help:

  % sudo tools/bpf/bpf.py
  usage: bpf.py [-h] {prog,p,map,m} ...

  drgn script to list BPF programs or maps and their properties
  unavailable via kernel API.

  See https://github.com/osandov/drgn/ for more details on drgn.

  optional arguments:
    -h, --help      show this help message and exit

  subcommands:
    {prog,p,map,m}
      prog (p)      list BPF programs
      map (m)       list BPF maps

[1] https://github.com/osandov/drgn/
[2] https://drgn.readthedocs.io/en/latest/index.html
[3] https://lwn.net/Articles/789641/
[4] https://github.com/osandov/drgn/commit/c8ef841768032e36581d45648e42fc2a5489d8f2

Signed-off-by: Andrey Ignatov <rdna@fb.com>
---
 tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
 create mode 100755 tools/bpf/bpf.py

diff --git a/tools/bpf/bpf.py b/tools/bpf/bpf.py
new file mode 100755
index 000000000000..a00d112c0486
--- /dev/null
+++ b/tools/bpf/bpf.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env drgn
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+#
+# Copyright (c) 2020 Facebook
+
+DESCRIPTION = """
+drgn script to list BPF programs or maps and their properties
+unavailable via kernel API.
+
+See https://github.com/osandov/drgn/ for more details on drgn.
+"""
+
+import argparse
+import sys
+
+from drgn.helpers import enum_type_to_class
+from drgn.helpers.linux import (
+    bpf_map_for_each,
+    bpf_prog_for_each,
+    hlist_for_each_entry,
+)
+
+
+BpfMapType = enum_type_to_class(prog.type("enum bpf_map_type"), "BpfMapType")
+BpfProgType = enum_type_to_class(prog.type("enum bpf_prog_type"), "BpfProgType")
+BpfProgTrampType = enum_type_to_class(
+    prog.type("enum bpf_tramp_prog_type"), "BpfProgTrampType"
+)
+BpfAttachType = enum_type_to_class(
+    prog.type("enum bpf_attach_type"), "BpfAttachType"
+)
+
+
+def get_btf_name(btf, btf_id):
+    type_ = btf.types[btf_id]
+    if type_.name_off < btf.hdr.str_len:
+        return btf.strings[type_.name_off].address_of_().string_().decode()
+    return ""
+
+
+def get_prog_btf_name(bpf_prog):
+    aux = bpf_prog.aux
+    if aux.btf:
+        # func_info[0] points to BPF program function itself.
+        return get_btf_name(aux.btf, aux.func_info[0].type_id)
+    return ""
+
+
+def get_prog_name(bpf_prog):
+    return get_prog_btf_name(bpf_prog) or bpf_prog.aux.name.string_().decode()
+
+
+def attach_type_to_tramp(attach_type):
+    at = BpfAttachType(attach_type)
+
+    if at == BpfAttachType.BPF_TRACE_FENTRY:
+        return BpfProgTrampType.BPF_TRAMP_FENTRY
+
+    if at == BpfAttachType.BPF_TRACE_FEXIT:
+        return BpfProgTrampType.BPF_TRAMP_FEXIT
+
+    return BpfProgTrampType.BPF_TRAMP_REPLACE
+
+
+def get_linked_func(bpf_prog):
+    kind = attach_type_to_tramp(bpf_prog.expected_attach_type)
+
+    linked_prog = bpf_prog.aux.linked_prog
+    linked_btf_id = bpf_prog.aux.attach_btf_id
+
+    linked_prog_id = linked_prog.aux.id.value_()
+    linked_name = "{}->{}()".format(
+        get_prog_name(linked_prog),
+        get_btf_name(linked_prog.aux.btf, linked_btf_id),
+    )
+
+    return "{}->{}: {} {}".format(
+        linked_prog_id, linked_btf_id.value_(), kind.name, linked_name
+    )
+
+
+def get_tramp_progs(bpf_prog):
+    tr = bpf_prog.aux.trampoline
+    if not tr:
+        return
+
+    if tr.extension_prog:
+        yield tr.extension_prog
+    else:
+        for head in tr.progs_hlist:
+            for tramp_aux in hlist_for_each_entry(
+                "struct bpf_prog_aux", head, "tramp_hlist"
+            ):
+                yield tramp_aux.prog
+
+
+def list_bpf_progs(args, prog):
+    for bpf_prog in bpf_prog_for_each(prog):
+        id_ = bpf_prog.aux.id.value_()
+        type_ = BpfProgType(bpf_prog.type).name
+        name = get_prog_name(bpf_prog)
+
+        linked = ", ".join(
+            [get_linked_func(p) for p in get_tramp_progs(bpf_prog)]
+        )
+        if linked:
+            linked = " linked:[{}]".format(linked)
+
+        print("{:>6}: {:32} {:32}{}".format(id_, type_, name, linked))
+
+
+def list_bpf_maps(args, prog):
+    for map_ in bpf_map_for_each(prog):
+        id_ = map_.id.value_()
+        type_ = BpfMapType(map_.map_type).name
+        name_ = map_.name.string_().decode()
+
+        print("{:>6}: {:32} {}".format(id_, type_, name_))
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter
+    )
+
+    subparsers = parser.add_subparsers(
+        title="subcommands", dest="subparser_name"
+    )
+
+    prog_parser = subparsers.add_parser(
+        "prog", aliases=["p"], help="list BPF programs"
+    )
+    prog_parser.set_defaults(func=list_bpf_progs)
+
+    map_parser = subparsers.add_parser(
+        "map", aliases=["m"], help="list BPF maps"
+    )
+    map_parser.set_defaults(func=list_bpf_maps)
+
+    args = parser.parse_args()
+    if not args.subparser_name:
+        parser.print_help()
+        sys.exit(2)
+
+    args.func(args, prog)  # prog is global drgn.Program provided by drgn.
+
+
+if __name__ == "__main__":
+    main()
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2020-02-28 21:30 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-27  2:32 [PATCH bpf-next] bpf: Add drgn script to list progs/maps Andrey Ignatov
2020-02-27  5:45 ` Song Liu
2020-02-27 17:01   ` Andrey Ignatov
2020-02-27  6:27 ` Andrii Nakryiko
2020-02-27 17:38   ` Andrey Ignatov
2020-02-27 18:01 ` Stanislav Fomichev
2020-02-27 18:26   ` Andrey Ignatov
2020-02-27 21:11     ` Daniel Borkmann
2020-02-27 21:32       ` Daniel Borkmann
2020-02-27 22:19         ` Omar Sandoval
2020-02-28 20:11           ` Andrey Ignatov
2020-02-28 21:29             ` Andrey Ignatov
2020-02-28 12:51 ` Quentin Monnet
2020-02-28 20:15   ` Andrey Ignatov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).