Quentin Monnet <quentin@xxxxxxxxxxxxx> [Fri, 2020-02-28 04:51 -0800]: > 2020-02-26 18:32 UTC-0800 ~ Andrey Ignatov <rdna@xxxxxx> > > 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. > > > > [...] > > > > > Signed-off-by: Andrey Ignatov <rdna@xxxxxx> > > --- > > 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" > > +) > > Hi Andrey, the script looks neat, thanks for this work! > > I tried to run it on my system. Because my kernel is 5.3 and does not have > "enum bpf_tramp_prog_type", the script crashes on the above assignments. But > even without that enum, it could be possible to print program and map ids > and types (even if we don't show the trampolines). > > Do you think it would be worth adding error handling on that block, > something like: > > try: > BpfMapType = ... > BpfProgType = ... > BpfProgTrampType = ... > BpfAttachType = ... > except LookupError as e: > print(e) # Possibly add a hint as kernel being too old? > > I understand that printing the BPF extensions is the main interest of the > script, I'm just thinking it would be nice to use it / tweak it even if not > on the latest kernel. What do you think? Hi Quentin, Thanks for feedback. That's a nice usability improvement indeed. I'll add something like this and tag you on the github PR (since we're coming to the conclusion that drgn repo is a better place for it). > > + > > + > > +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 > > Same observation here, I solved it with > > try: > tr = bpf_prog.aux.trampoline > if not tr: > return > except AttributeError as e: > print(e) > return Yep, sounds good. Will address in v2 on github as well. Thanks! -- Andrey Ignatov