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

Proposal: contrib/bpf_inspect.py: disas bpf prog with capstone #409

Draft
wants to merge 1 commit 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
126 changes: 120 additions & 6 deletions contrib/bpf_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import sys
import drgn
import argparse
import platform

from drgn import container_of
from drgn.helpers.common.type import enum_type_to_class
from drgn.helpers.linux import (
idr_find,
bpf_map_for_each,
bpf_prog_for_each,
bpf_link_for_each,
Expand Down Expand Up @@ -43,6 +45,50 @@ def bpf_attach_type_to_tramp(attach_type):
return BpfProgTrampType.BPF_TRAMP_REPLACE


def bpf_disas(addr, len):
try:
import capstone
except ImportError:
sys.exit("Disassembly requires capstone")

arch = platform.machine()
if arch == "x86_64":
cs_arch = capstone.CS_ARCH_X86
cs_mode = capstone.CS_MODE_64
elif arch == "aarch64":
cs_arch = capstone.CS_ARCH_ARM64
cs_mode = capstone.CS_MODE_ARM
else:
sys.exit("Disassembly is only supported on x86_64 and aarch64")

b = prog.read(addr, len)
md = capstone.Cs(cs_arch, cs_mode)
for insn in md.disasm(b, addr):
opcode = " ".join([f"{byte:02x}" for byte in insn.bytes])
yield f"0x{insn.address:x}:\t{opcode:19}\t{insn.mnemonic}\t{insn.op_str}"


class BpfKsym(object):
def __init__(self, ksym):
self.ksym = ksym

def disas(self):
start = self.ksym.start.value_()
end = self.ksym.end.value_()

return bpf_disas(start, end - start)

@property
def name(self):
return self.ksym.name.string_().decode()

def __repr__(self):
start = self.ksym.start.value_()
end = self.ksym.end.value_()

return f"{self.name:40} 0x{start:018x} {end - start} bytes"


class BpfTramp(object):
def __init__(self, tr):
self.tr = tr
Expand All @@ -64,6 +110,29 @@ def get_progs(self):
except LookupError:
return

def get_ksym(self):
if not self.tr:
return None

try:
return BpfKsym(self.tr.cur_image.member_("ksym"))
except LookupError:
return None

def disas(self):
if not self.tr:
return

ksym = self.get_ksym()
if ksym:
return ksym.disas()

img = self.tr.cur_image
return bpf_disas(img.image.value_(), img.size.value_())

def __repr__(self):
return f"{self.get_ksym()}"


class BpfMap(object):
def __init__(self, bpf_map):
Expand Down Expand Up @@ -111,12 +180,15 @@ def get_btf_name(self):
return self.__get_btf_name(aux.btf, aux.func_info[0].type_id)
return ""

def get_ksym_name(self):
def get_ksym(self):
try:
ksym = self.prog.aux.member_("ksym")
return ksym.name.string_().decode()[26:]
return BpfKsym(self.prog.aux.member_("ksym"))
except LookupError:
return ""
return None

def get_ksym_name(self):
ksym = self.get_ksym()
return ksym.name[26:] if ksym else ""

def get_prog_name(self):
if self.is_subprog():
Expand All @@ -131,6 +203,11 @@ def get_subprogs(self):
for i in range(0, self.prog.aux.func_cnt.value_()):
yield i, BpfProg(self.prog.aux.func[i])

def get_subprog(self, index):
if index >= self.prog.aux.func_cnt.value_():
return None
return BpfProg(self.prog.aux.func[index])

def get_linked_func(self):
kind = bpf_attach_type_to_tramp(self.prog.expected_attach_type)

Expand All @@ -147,7 +224,7 @@ def get_linked_func(self):

def get_attach_func(self):
try:
func_ = self.prog.aux.attach_func_name
func_ = self.prog.aux.member_("attach_func_name")
if func_:
return func_.string_().decode()
except LookupError:
Expand Down Expand Up @@ -175,6 +252,12 @@ def get_tramp_progs(self):

return BpfTramp(tr).get_progs()

def disas(self):
if self.prog.jited.value_():
ksym = self.get_ksym()
if ksym:
return ksym.disas()

def __repr__(self):
id_ = self.prog.aux.id.value_()
type_ = BpfProgType(self.prog.type).name
Expand Down Expand Up @@ -202,14 +285,30 @@ def list_bpf_progs(show_details=False):
for map_ in bpf_prog.get_used_maps():
print(f"\t{'used map:':9} {map_}")

ksym = bpf_prog.get_ksym()
if ksym:
print(f"\t{'ksym:':9} {ksym}")

for index, subprog in bpf_prog.get_subprogs():
if index == 0:
continue

print(f"\t{f'func[{index:>2}]:':9} {subprog}")
ksym = subprog.get_ksym()
if ksym:
print(f"\t{'funcksym:':9} {ksym}")


def __list_bpf_progs(args):
list_bpf_progs(args.show_details)


def get_bpf_prog_by_id(id):
type_ = prog.type("struct bpf_prog *")
prog_ = idr_find(prog["prog_idr"].address_of_(), id)
return BpfProg(drgn.cast(type_, prog_))


class BpfProgArrayMap(BpfMap):
def __init__(self, bpf_map):
super().__init__(bpf_map)
Expand Down Expand Up @@ -282,6 +381,12 @@ def __list_bpf_maps(args):
list_bpf_maps(args.show_details)


def get_bpf_map_by_id(id):
type_ = prog.type("struct bpf_map *")
map_ = idr_find(prog["map_idr"].address_of_(), id)
return BpfMap(drgn.cast(type_, map_))


class BpfLink(object):
def __init__(self, bpf_link):
self.link = bpf_link
Expand All @@ -301,8 +406,11 @@ def __init__(self, link):
def get_tgt_prog(self):
return self.tracing.tgt_prog

def get_tramp(self):
return BpfTramp(self.tracing.trampoline)

def get_linked_progs(self):
return BpfTramp(self.tracing.trampoline).get_progs()
return self.get_tramp().get_progs()

def __repr__(self):
tgt_prog = self.get_tgt_prog()
Expand Down Expand Up @@ -380,6 +488,12 @@ def __list_bpf_links(args):
list_bpf_links(args.show_details)


def get_bpf_link_by_id(id):
type_ = prog.type("struct bpf_link *")
link_ = idr_find(prog["link_idr"].address_of_(), id)
return BpfLink(drgn.cast(type_, link_))


def __run_interactive(args):
try:
from drgn.cli import run_interactive
Expand Down