254 lines
7.6 KiB
Python
254 lines
7.6 KiB
Python
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
import json
|
|
import subprocess
|
|
import tempfile
|
|
|
|
import gdb
|
|
|
|
from linux import constants, lists, radixtree, utils
|
|
|
|
|
|
if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT:
|
|
bpf_ksym_type = utils.CachedType("struct bpf_ksym")
|
|
if constants.LX_CONFIG_BPF_SYSCALL:
|
|
bpf_prog_type = utils.CachedType("struct bpf_prog")
|
|
|
|
|
|
def get_ksym_name(ksym):
|
|
name = ksym["name"].bytes
|
|
end = name.find(b"\x00")
|
|
if end != -1:
|
|
name = name[:end]
|
|
return name.decode()
|
|
|
|
|
|
def list_ksyms():
|
|
if not (constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT):
|
|
return []
|
|
bpf_kallsyms = gdb.parse_and_eval("&bpf_kallsyms")
|
|
bpf_ksym_ptr_type = bpf_ksym_type.get_type().pointer()
|
|
return list(lists.list_for_each_entry(bpf_kallsyms,
|
|
bpf_ksym_ptr_type,
|
|
"lnode"))
|
|
|
|
|
|
class KsymAddBreakpoint(gdb.Breakpoint):
|
|
def __init__(self, monitor):
|
|
super(KsymAddBreakpoint, self).__init__("bpf_ksym_add", internal=True)
|
|
self.silent = True
|
|
self.monitor = monitor
|
|
|
|
def stop(self):
|
|
self.monitor.add(gdb.parse_and_eval("ksym"))
|
|
return False
|
|
|
|
|
|
class KsymRemoveBreakpoint(gdb.Breakpoint):
|
|
def __init__(self, monitor):
|
|
super(KsymRemoveBreakpoint, self).__init__("bpf_ksym_del",
|
|
internal=True)
|
|
self.silent = True
|
|
self.monitor = monitor
|
|
|
|
def stop(self):
|
|
self.monitor.remove(gdb.parse_and_eval("ksym"))
|
|
return False
|
|
|
|
|
|
class KsymMonitor:
|
|
def __init__(self, add, remove):
|
|
self.add = add
|
|
self.remove = remove
|
|
|
|
self.add_bp = KsymAddBreakpoint(self)
|
|
self.remove_bp = KsymRemoveBreakpoint(self)
|
|
|
|
self.notify_initial()
|
|
|
|
def notify_initial(self):
|
|
for ksym in list_ksyms():
|
|
self.add(ksym)
|
|
|
|
def delete(self):
|
|
self.add_bp.delete()
|
|
self.remove_bp.delete()
|
|
|
|
|
|
def list_progs():
|
|
if not constants.LX_CONFIG_BPF_SYSCALL:
|
|
return []
|
|
idr_rt = gdb.parse_and_eval("&prog_idr.idr_rt")
|
|
bpf_prog_ptr_type = bpf_prog_type.get_type().pointer()
|
|
progs = []
|
|
for _, slot in radixtree.for_each_slot(idr_rt):
|
|
prog = slot.dereference().cast(bpf_prog_ptr_type)
|
|
progs.append(prog)
|
|
# Subprogs are not registered in prog_idr, fetch them manually.
|
|
# func[0] is the current prog.
|
|
aux = prog["aux"]
|
|
func = aux["func"]
|
|
real_func_cnt = int(aux["real_func_cnt"])
|
|
for i in range(1, real_func_cnt):
|
|
progs.append(func[i])
|
|
return progs
|
|
|
|
|
|
class ProgAddBreakpoint(gdb.Breakpoint):
|
|
def __init__(self, monitor):
|
|
super(ProgAddBreakpoint, self).__init__("bpf_prog_kallsyms_add",
|
|
internal=True)
|
|
self.silent = True
|
|
self.monitor = monitor
|
|
|
|
def stop(self):
|
|
self.monitor.add(gdb.parse_and_eval("fp"))
|
|
return False
|
|
|
|
|
|
class ProgRemoveBreakpoint(gdb.Breakpoint):
|
|
def __init__(self, monitor):
|
|
super(ProgRemoveBreakpoint, self).__init__("bpf_prog_free_id",
|
|
internal=True)
|
|
self.silent = True
|
|
self.monitor = monitor
|
|
|
|
def stop(self):
|
|
self.monitor.remove(gdb.parse_and_eval("prog"))
|
|
return False
|
|
|
|
|
|
class ProgMonitor:
|
|
def __init__(self, add, remove):
|
|
self.add = add
|
|
self.remove = remove
|
|
|
|
self.add_bp = ProgAddBreakpoint(self)
|
|
self.remove_bp = ProgRemoveBreakpoint(self)
|
|
|
|
self.notify_initial()
|
|
|
|
def notify_initial(self):
|
|
for prog in list_progs():
|
|
self.add(prog)
|
|
|
|
def delete(self):
|
|
self.add_bp.delete()
|
|
self.remove_bp.delete()
|
|
|
|
|
|
def btf_str_by_offset(btf, offset):
|
|
while offset < btf["start_str_off"]:
|
|
btf = btf["base_btf"]
|
|
|
|
offset -= btf["start_str_off"]
|
|
if offset < btf["hdr"]["str_len"]:
|
|
return (btf["strings"] + offset).string()
|
|
|
|
return None
|
|
|
|
|
|
def bpf_line_info_line_num(line_col):
|
|
return line_col >> 10
|
|
|
|
|
|
def bpf_line_info_line_col(line_col):
|
|
return line_col & 0x3ff
|
|
|
|
|
|
class LInfoIter:
|
|
def __init__(self, prog):
|
|
# See bpf_prog_get_file_line() for details.
|
|
self.pos = 0
|
|
self.nr_linfo = 0
|
|
|
|
if prog is None:
|
|
return
|
|
|
|
self.bpf_func = int(prog["bpf_func"])
|
|
aux = prog["aux"]
|
|
self.btf = aux["btf"]
|
|
linfo_idx = aux["linfo_idx"]
|
|
self.nr_linfo = int(aux["nr_linfo"]) - linfo_idx
|
|
if self.nr_linfo == 0:
|
|
return
|
|
|
|
linfo_ptr = aux["linfo"]
|
|
tpe = linfo_ptr.type.target().array(self.nr_linfo).pointer()
|
|
self.linfo = (linfo_ptr + linfo_idx).cast(tpe).dereference()
|
|
jited_linfo_ptr = aux["jited_linfo"]
|
|
tpe = jited_linfo_ptr.type.target().array(self.nr_linfo).pointer()
|
|
self.jited_linfo = (jited_linfo_ptr + linfo_idx).cast(tpe).dereference()
|
|
|
|
self.filenos = {}
|
|
|
|
def get_code_off(self):
|
|
if self.pos >= self.nr_linfo:
|
|
return -1
|
|
return self.jited_linfo[self.pos] - self.bpf_func
|
|
|
|
def advance(self):
|
|
self.pos += 1
|
|
|
|
def get_fileno(self):
|
|
file_name_off = int(self.linfo[self.pos]["file_name_off"])
|
|
fileno = self.filenos.get(file_name_off)
|
|
if fileno is not None:
|
|
return fileno, None
|
|
file_name = btf_str_by_offset(self.btf, file_name_off)
|
|
fileno = len(self.filenos) + 1
|
|
self.filenos[file_name_off] = fileno
|
|
return fileno, file_name
|
|
|
|
def get_line_col(self):
|
|
line_col = int(self.linfo[self.pos]["line_col"])
|
|
return bpf_line_info_line_num(line_col), \
|
|
bpf_line_info_line_col(line_col)
|
|
|
|
|
|
def generate_debug_obj(ksym, prog):
|
|
name = get_ksym_name(ksym)
|
|
# Avoid read_memory(); it throws bogus gdb.MemoryError in some contexts.
|
|
start = ksym["start"]
|
|
code = start.cast(gdb.lookup_type("unsigned char")
|
|
.array(int(ksym["end"]) - int(start))
|
|
.pointer()).dereference().bytes
|
|
linfo_iter = LInfoIter(prog)
|
|
|
|
result = tempfile.NamedTemporaryFile(suffix=".o", mode="wb")
|
|
try:
|
|
with tempfile.NamedTemporaryFile(suffix=".s", mode="w") as src:
|
|
# ".loc" does not apply to ".byte"s, only to ".insn"s, but since
|
|
# this needs to work for all architectures, the latter are not an
|
|
# option. Ask the assembler to apply ".loc"s to labels as well,
|
|
# and generate dummy labels after each ".loc".
|
|
src.write(".loc_mark_labels 1\n")
|
|
|
|
src.write(".globl {}\n".format(name))
|
|
src.write(".type {},@function\n".format(name))
|
|
src.write("{}:\n".format(name))
|
|
for code_off, code_byte in enumerate(code):
|
|
if linfo_iter.get_code_off() == code_off:
|
|
fileno, file_name = linfo_iter.get_fileno()
|
|
if file_name is not None:
|
|
src.write(".file {} {}\n".format(
|
|
fileno, json.dumps(file_name)))
|
|
line, col = linfo_iter.get_line_col()
|
|
src.write(".loc {} {} {}\n".format(fileno, line, col))
|
|
src.write("0:\n")
|
|
linfo_iter.advance()
|
|
src.write(".byte {}\n".format(code_byte))
|
|
src.write(".size {},{}\n".format(name, len(code)))
|
|
src.flush()
|
|
|
|
try:
|
|
subprocess.check_call(["as", "-c", src.name, "-o", result.name])
|
|
except FileNotFoundError:
|
|
# "as" is not installed.
|
|
result.close()
|
|
return None
|
|
return result
|
|
except:
|
|
result.close()
|
|
raise
|