Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BPF: Add helpers for BPF links and BTF objects, update tools/ script to work with Linux 5.10+ #152

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 89 additions & 14 deletions tools/bpf_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@

import argparse

from drgn import container_of
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
from drgn.helpers.linux import (
bpf_btf_for_each,
bpf_link_for_each,
bpf_map_for_each,
bpf_prog_for_each,
hlist_for_each_entry,
)

try:
BpfLinkType = enum_type_to_class(prog.type("enum bpf_link_type"), "BpfLinkType")
except LookupError:
BpfLinkType = None

BpfMapType = enum_type_to_class(prog.type("enum bpf_map_type"), "BpfMapType")
BpfProgType = enum_type_to_class(prog.type("enum bpf_prog_type"), "BpfProgType")
Expand Down Expand Up @@ -50,10 +62,9 @@ def attach_type_to_tramp(attach_type):
return BpfProgTrampType.BPF_TRAMP_REPLACE


def get_linked_func(bpf_prog):
def get_linked_func(bpf_prog, linked_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 = (
Expand All @@ -64,15 +75,7 @@ def get_linked_func(bpf_prog):
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

def get_progs_from_tramp(trampoline):
if not tr:
return

Expand All @@ -86,13 +89,53 @@ def get_tramp_progs(bpf_prog):
yield tramp_aux.prog


def get_progs_from_tracing_link(bpf_prog):
for link in bpf_link_for_each(prog):
if link.prog.aux.id.value_() == bpf_prog.aux.id.value_():
tr_link = container_of(link, "struct bpf_tracing_link", "link")
if tr_link and tr_link.tgt_prog:
yield tr_link.tgt_prog


def get_tramp_progs(bpf_prog):
# dst_rampoline replaces "trampoline" since Linux kernel commit
# 3aac1ead5eb6 ("bpf: Move prog->aux->linked_prog and trampoline into
# bpf_link on attach") (in v5.10).
try:
tramp = bpf_prog.aux.member_("dst_trampoline")
if tramp:
yield from get_progs_from_tramp(tramp)
else:
yield from get_progs_from_tracing_link(bpf_prog)
except LookupError:
# Before that, "trampoline" is available since Linux kernel commit
# fec56f5890d9 ("bpf: Introduce BPF trampoline") (in v5.5).
try:
tramp = bpf_prog.aux.member_("trampoline")
except LookupError:
# This kernel doesn't support trampolines at all.
return
yield from get_progs_from_tramp(tramp)


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)])
progs = []
for p in get_tramp_progs(bpf_prog):
try:
linked_prog = p.aux.member_("linked_prog")
caller_prog = p
except LookupError:
# Linux 5.10+
linked_prog = p
caller_prog = bpf_prog
progs.append(get_linked_func(caller_prog, linked_prog))

linked = ", ".join(progs)
if linked:
linked = f" linked:[{linked}]"

Expand All @@ -108,9 +151,35 @@ def list_bpf_maps(args):
print(f"{id_:>6}: {type_:32} {name}")


def list_bpf_btf(args):
for btf in bpf_btf_for_each(prog):
id_ = btf.id.value_()
name = btf.name.string_().decode() or "<anon>"
kernel = "kernel" if btf.kernel_btf.value_() == 1 else ""

print(f"{id_:>6}: {kernel:6} {name}")


def list_bpf_links(args):
if BpfLinkType is None:
# BpfLinkType was not initialized properly, likely because the kernel
# is too old to support link. So there is no link to list, simply
# return.
return

for link in bpf_link_for_each(prog):
id_ = link.id.value_()
type_ = BpfLinkType(link.type).name
prog_id = ""
if link.prog:
prog_id = link.prog.aux.id.value_()

print(f"{id_:>6}: {type_:32} {prog_id:>6}")


def main():
parser = argparse.ArgumentParser(
description="drgn script to list BPF programs or maps and their properties unavailable via kernel API"
description="drgn script to list BPF objects and their properties unavailable via kernel API"
)

subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
Expand All @@ -122,6 +191,12 @@ def main():
map_parser = subparsers.add_parser("map", aliases=["m"], help="list BPF maps")
map_parser.set_defaults(func=list_bpf_maps)

link_parser = subparsers.add_parser("link", aliases=["l"], help="list BPF links")
link_parser.set_defaults(func=list_bpf_links)

btf_parser = subparsers.add_parser("btf", aliases=["b"], help="list BTF objects")
btf_parser.set_defaults(func=list_bpf_btf)

args = parser.parse_args()
args.func(args)

Expand Down