mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-24 02:03:05 +00:00
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:
parent
fddabbfad0
commit
6cbdebc269
150
tools/bpf_inspect.py
Executable file
150
tools/bpf_inspect.py
Executable 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()
|
Loading…
Reference in New Issue
Block a user