Skip to content

Commit

Permalink
contrib/bpf_inspect.py: disas bpf prog with capstone
Browse files Browse the repository at this point in the history
It is really useful to disassemble bpf prog by drgn instead of gdb.

In this commit, it uses [capstone](https://github.com/capstone-engine/capstone)
to disassemble bpf prog, as it can get address from bpf prog's ksym.

Meanwhile, it is able to disassemble trampoline of fentry/fexit bpf prog.

Signed-off-by: Leon Hwang <[email protected]>
  • Loading branch information
Asphaltt committed Jun 26, 2024
1 parent 7ff4906 commit 1c6aade
Showing 1 changed file with 107 additions and 6 deletions.
113 changes: 107 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:32} 0x{start:018x} {end - start} bytes"


class BpfTramp(object):
def __init__(self, tr):
self.tr = tr
Expand All @@ -64,6 +110,26 @@ 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_())


class BpfMap(object):
def __init__(self, bpf_map):
Expand Down Expand Up @@ -111,12 +177,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 +200,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 +221,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 +249,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 @@ -210,6 +290,12 @@ 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 +368,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 +393,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 +475,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

0 comments on commit 1c6aade

Please sign in to comment.