tools: Add drgn script to list progs/maps

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

The script was initially sent to kernel tree [1] but it was agreed that
drgn repo is a better place for it and it's a good idea to create
`tools/` directory in drgn to keep tools likes this. See [2] for
details.

The main use-case bpf_inspect.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, for example,
bpftool can show all BPF programs but can't show if program A replaces a
function in program B.

Example:

  % sudo tools/bpf_inspect.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.

More examples of output:

  % sudo tools/bpf_inspect.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_inspect.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_inspect.py
  usage: bpf_inspect.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://lore.kernel.org/bpf/20200228201514.GB51456@rdna-mbp/T/
[2] https://lore.kernel.org/bpf/20200228201514.GB51456@rdna-mbp/T/#mefed65e8a98116bd5d07d09a570a3eac46724951

Signed-off-by: Andrey Ignatov <rdna@fb.com>
This commit is contained in:
Andrey Ignatov 2020-02-28 12:32:57 -08:00 committed by Omar Sandoval
parent fddabbfad0
commit 6cbdebc269

150
tools/bpf_inspect.py Executable file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env drgn
# SPDX-License-Identifier: GPL-3.0+
#
# Copyright (c) 2020 Facebook
DESCRIPTION = """
drgn script to list BPF programs or maps and their properties
unavailable via kernel API.
"""
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")
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):
# bpf_tramp_prog_type is available since linux kernel 5.5, this code should
# be called only after checking for bpf_prog.aux.trampoline to be present
# though so no error checking here.
BpfProgTrampType = enum_type_to_class(
prog.type("enum bpf_tramp_prog_type"), "BpfProgTrampType"
)
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_prog_id = linked_prog.aux.id.value_()
linked_btf_id = bpf_prog.aux.attach_btf_id.value_()
linked_name = (
f"{get_prog_name(linked_prog)}->"
f"{get_btf_name(linked_prog.aux.btf, linked_btf_id)}()"
)
return f"{linked_prog_id}->{linked_btf_id}: {kind.name} {linked_name}"
def get_tramp_progs(bpf_prog):
try:
tr = bpf_prog.aux.member_("trampoline")
except LookupError:
# Trampoline is available since Linux kernel commit
# fec56f5890d9 ("bpf: Introduce BPF trampoline") (in v5.5).
# Skip trampoline if current kernel doesn't support it.
return
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):
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 = f" linked:[{linked}]"
print(f"{id_:>6}: {type_:32} {name:32} {linked}")
def list_bpf_maps(args):
for map_ in bpf_map_for_each(prog):
id_ = map_.id.value_()
type_ = BpfMapType(map_.map_type).name
name = map_.name.string_().decode()
print(f"{id_:>6}: {type_:32} {name}")
def main():
parser = argparse.ArgumentParser(
description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter
)
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
subparsers.required = True
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()
args.func(args)
if __name__ == "__main__":
main()