Skip to content
Open
Show file tree
Hide file tree
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
130 changes: 130 additions & 0 deletions drgn/commands/_builtin/crash/_bpf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later

# ebpf-related commands.

import argparse
from typing import Any, List, Sequence

from drgn import Program
from drgn.commands import drgn_argument
from drgn.commands.crash import CrashDrgnCodeBuilder, crash_command
from drgn.helpers.common.format import CellFormat, print_table
from drgn.helpers.linux.bpf import (
bpf_map_for_each,
bpf_prog_for_each,
bpf_prog_used_maps,
)


@crash_command(
description="display all eBPF programs and maps",
arguments=(drgn_argument,),
)
def _crash_cmd_bpf(
prog: Program, name: str, args: argparse.Namespace, **kwargs: Any
) -> None:
if args.drgn:
code = CrashDrgnCodeBuilder(prog)
code.add_from_import(
"drgn.helpers.linux.bpf",
"bpf_map_for_each",
"bpf_prog_for_each",
"bpf_prog_used_maps",
)
code.append(
"""\
for bpf_prog in bpf_prog_for_each(prog):
aux = bpf_prog.aux
prog_id = aux.id
prog_type = bpf_prog.type
tag = bpf_prog.tag
for used_map in bpf_prog_used_maps(bpf_prog):
map_id = used_map.id

for bpf_map in bpf_map_for_each(prog):
map_id = bpf_map.id
map_type = bpf_map.map_type
try:
map_flags = bpf_map.map_flags
except AttributeError:
# map_flags is only available since Linux 4.6.
pass
"""
)
code.print()
return

prog_rows: List[Sequence[Any]] = [
(
CellFormat("ID", "^"),
CellFormat("BPF_PROG", "^"),
CellFormat("BPF_PROG_AUX", "^"),
CellFormat("BPF_PROG_TYPE", "^"),
CellFormat("TAG", "^"),
CellFormat("USED_MAPS", "^"),
)
]

for bpf_prog in bpf_prog_for_each(prog):
prog_id = bpf_prog.aux.id.value_()
prog_type_name = bpf_prog.type.format_(type_name=False).split("BPF_PROG_TYPE_")[
-1
]

tag = bpf_prog.tag
prog_tag = "".join(f"{b.value_():02x}" for b in tag)

used_maps = []
for map in bpf_prog_used_maps(bpf_prog):
used_maps.append(map.id.value_())
used_maps_str = ",".join(str(m) for m in used_maps)

prog_rows.append(
(
prog_id,
CellFormat(bpf_prog.value_(), "^x"),
CellFormat(bpf_prog.aux.value_(), "^x"),
CellFormat(prog_type_name, "^"),
CellFormat(prog_tag, "^"),
CellFormat(used_maps_str, "^"),
)
)

print_table(prog_rows)
print()

map_rows: List[Sequence[Any]] = [
(
CellFormat("ID", "^"),
CellFormat("BPF_MAP", "^"),
CellFormat("BPF_MAP_TYPE", "^"),
CellFormat("MAP_FLAGS", "^"),
)
]

for bpf_map in bpf_map_for_each(prog):
map_id = bpf_map.id.value_()

map_type_name = bpf_map.map_type.format_(type_name=False).split(
"BPF_MAP_TYPE_"
)[-1]

# The 'map_flags' field was added in Linux kernel commit 6c9059817432
# ("bpf: pre-allocate hash map elements") in version 4.6. Older kernels
# may not have this field, so we catch LookupError and default to 0.
try:
map_flags = f"{bpf_map.map_flags.value_():08x}"
except AttributeError:
map_flags = "00000000"

map_rows.append(
(
map_id,
CellFormat(bpf_map.value_(), "^x"),
CellFormat(map_type_name, "^"),
CellFormat(map_flags, "^"),
)
)

print_table(map_rows)
12 changes: 12 additions & 0 deletions drgn/helpers/linux/bpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"bpf_prog_for_each",
"cgroup_bpf_prog_for_each",
"cgroup_bpf_prog_for_each_effective",
"bpf_prog_used_maps",
)


Expand Down Expand Up @@ -180,3 +181,14 @@ def cgroup_bpf_prog_for_each_effective(
if not prog:
break
yield prog


def bpf_prog_used_maps(bpf_prog: Object) -> Iterator[Object]:
"""
Yield maps used by a BPF program.

:param bpf_prog: ``struct bpf_prog *``
:return: Iterator of ``struct bpf_map *`` objects.
"""
aux = bpf_prog.aux.read_()
return iter(aux.used_maps[: aux.used_map_cnt])
177 changes: 176 additions & 1 deletion tests/linux_kernel/bpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import array
import ctypes
from typing import NamedTuple
import sys
from typing import NamedTuple, Tuple

from _drgn_util.platform import SYS
from tests.linux_kernel import _check_ctypes_syscall, _syscall
Expand Down Expand Up @@ -164,6 +165,180 @@
BPF_F_REPLACE = 1 << 2


# Instruction classes.
BPF_LD = 0x00
BPF_LDX = 0x01
BPF_ST = 0x02
BPF_STX = 0x03
BPF_ALU = 0x04
BPF_JMP = 0x05
BPF_RET = 0x06
BPF_JMP32 = 0x06
BPF_MISC = 0x07
BPF_ALU64 = 0x07

# ld/ldx fields.
BPF_W = 0x00
BPF_H = 0x08
BPF_B = 0x10
BPF_DW = 0x18
BPF_IMM = 0x00
BPF_ABS = 0x20
BPF_IND = 0x40
BPF_MEM = 0x60
BPF_LEN = 0x80
BPF_MSH = 0xA0
BPF_MEMSX = 0x80
BPF_ATOMIC = 0xC0
BPF_XADD = 0xC0

# alu/jmp fields.
BPF_ADD = 0x00
BPF_SUB = 0x10
BPF_MUL = 0x20
BPF_DIV = 0x30
BPF_OR = 0x40
BPF_AND = 0x50
BPF_LSH = 0x60
BPF_RSH = 0x70
BPF_NEG = 0x80
BPF_MOD = 0x90
BPF_XOR = 0xA0

BPF_JA = 0x00
BPF_JEQ = 0x10
BPF_JGT = 0x20
BPF_JGE = 0x30
BPF_JSET = 0x40
BPF_K = 0x00
BPF_X = 0x08

BPF_MOV = 0xB0
BPF_ARSH = 0xC0

# Change endianness of a register.
BPF_END = 0xD0
BPF_TO_LE = 0x00
BPF_TO_BE = 0x08
BPF_FROM_LE = BPF_TO_LE
BPF_FROM_BE = BPF_TO_BE

# jmp encodings.
BPF_JNE = 0x50
BPF_JLT = 0xA0
BPF_JLE = 0xB0
BPF_JSGT = 0x60
BPF_JSGE = 0x70
BPF_JSLT = 0xC0
BPF_JSLE = 0xD0
BPF_JCOND = 0xE0
BPF_CALL = 0x80
BPF_EXIT = 0x90

# atomic op type fields.
BPF_FETCH = 0x01
BPF_XCHG = 0xE0 | BPF_FETCH
BPF_CMPXCHG = 0xF0 | BPF_FETCH

BPF_LOAD_ACQ = 0x100
BPF_STORE_REL = 0x110

# Register numbers.
BPF_REG_0 = 0
BPF_REG_1 = 1
BPF_REG_2 = 2
BPF_REG_3 = 3
BPF_REG_4 = 4
BPF_REG_5 = 5
BPF_REG_6 = 6
BPF_REG_7 = 7
BPF_REG_8 = 8
BPF_REG_9 = 9
BPF_REG_10 = 10

MAX_BPF_REG = 11


# Instruction encoding.
def bpf_insn(*, code: int, dst_reg: int, src_reg: int, off: int, imm: int) -> int:
if code < 0 or code > 255:
raise ValueError(f"code {code} is out of range")
if dst_reg < 0 or dst_reg > 15:
raise ValueError(f"dst_reg {dst_reg} is out of range")
if src_reg < 0 or src_reg > 15:
raise ValueError(f"src_reg {src_reg} is out of range")
if off < -32768 or off > 32767:
raise ValueError(f"off {off} is out of range")
# Allow signed and unsigned 32-bit values.
if imm < -(2**31) or imm >= 2**32:
raise ValueError(f"imm {imm} is out of range")
if sys.byteorder == "little":
return (
code
| (dst_reg << 8)
| (src_reg << 12)
| ((off & 0xFFFF) << 16)
| ((imm & 0xFFFFFFFF) << 32)
)
else:
return (
(code << 56)
| (dst_reg << 52)
| (src_reg << 48)
| ((off & 0xFFFF) << 32)
| (imm & 0xFFFFFFFF)
)


# Instructions.
def BPF_MOV64_IMM(dst: int, imm: int) -> int:
return bpf_insn(
code=BPF_ALU64 | BPF_MOV | BPF_K,
dst_reg=dst,
src_reg=0,
off=0,
imm=imm,
)


BPF_PSEUDO_MAP_FD = 1
BPF_PSEUDO_MAP_IDX = 5
BPF_PSEUDO_MAP_VALUE = 2
BPF_PSEUDO_MAP_IDX_VALUE = 6
BPF_PSEUDO_BTF_ID = 3
BPF_PSEUDO_FUNC = 4


def BPF_LD_IMM64_RAW(dst: int, src: int, imm: int) -> Tuple[int, int]:
# Allow signed and unsigned 64-bit values.
if imm < -(2**63) or imm >= 2**64:
raise ValueError(f"imm {imm} is out of range")
return (
bpf_insn(
code=BPF_LD | BPF_DW | BPF_IMM,
dst_reg=dst,
src_reg=src,
off=0,
imm=imm & 0xFFFFFFFF,
),
bpf_insn(code=0, dst_reg=0, src_reg=0, off=0, imm=(imm >> 32) & 0xFFFFFFFF),
)


def BPF_LD_MAP_FD(dst: int, map_fd: int) -> int:
return BPF_LD_IMM64_RAW(dst, BPF_PSEUDO_MAP_FD, map_fd)


def BPF_EXIT_INSN() -> int:
return bpf_insn(
code=BPF_JMP | BPF_EXIT,
dst_reg=0,
src_reg=0,
off=0,
imm=0,
)


class _bpf_attr_map_create(ctypes.Structure):
_fields_ = (
("map_type", ctypes.c_uint32),
Expand Down
Loading
Loading