bpf.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # SPDX-License-Identifier: GPL-2.0
  2. import json
  3. import subprocess
  4. import tempfile
  5. import gdb
  6. from linux import constants, lists, radixtree, utils
  7. if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT:
  8. bpf_ksym_type = utils.CachedType("struct bpf_ksym")
  9. if constants.LX_CONFIG_BPF_SYSCALL:
  10. bpf_prog_type = utils.CachedType("struct bpf_prog")
  11. def get_ksym_name(ksym):
  12. name = ksym["name"].bytes
  13. end = name.find(b"\x00")
  14. if end != -1:
  15. name = name[:end]
  16. return name.decode()
  17. def list_ksyms():
  18. if not (constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT):
  19. return []
  20. bpf_kallsyms = gdb.parse_and_eval("&bpf_kallsyms")
  21. bpf_ksym_ptr_type = bpf_ksym_type.get_type().pointer()
  22. return list(lists.list_for_each_entry(bpf_kallsyms,
  23. bpf_ksym_ptr_type,
  24. "lnode"))
  25. class KsymAddBreakpoint(gdb.Breakpoint):
  26. def __init__(self, monitor):
  27. super(KsymAddBreakpoint, self).__init__("bpf_ksym_add", internal=True)
  28. self.silent = True
  29. self.monitor = monitor
  30. def stop(self):
  31. self.monitor.add(gdb.parse_and_eval("ksym"))
  32. return False
  33. class KsymRemoveBreakpoint(gdb.Breakpoint):
  34. def __init__(self, monitor):
  35. super(KsymRemoveBreakpoint, self).__init__("bpf_ksym_del",
  36. internal=True)
  37. self.silent = True
  38. self.monitor = monitor
  39. def stop(self):
  40. self.monitor.remove(gdb.parse_and_eval("ksym"))
  41. return False
  42. class KsymMonitor:
  43. def __init__(self, add, remove):
  44. self.add = add
  45. self.remove = remove
  46. self.add_bp = KsymAddBreakpoint(self)
  47. self.remove_bp = KsymRemoveBreakpoint(self)
  48. self.notify_initial()
  49. def notify_initial(self):
  50. for ksym in list_ksyms():
  51. self.add(ksym)
  52. def delete(self):
  53. self.add_bp.delete()
  54. self.remove_bp.delete()
  55. def list_progs():
  56. if not constants.LX_CONFIG_BPF_SYSCALL:
  57. return []
  58. idr_rt = gdb.parse_and_eval("&prog_idr.idr_rt")
  59. bpf_prog_ptr_type = bpf_prog_type.get_type().pointer()
  60. progs = []
  61. for _, slot in radixtree.for_each_slot(idr_rt):
  62. prog = slot.dereference().cast(bpf_prog_ptr_type)
  63. progs.append(prog)
  64. # Subprogs are not registered in prog_idr, fetch them manually.
  65. # func[0] is the current prog.
  66. aux = prog["aux"]
  67. func = aux["func"]
  68. real_func_cnt = int(aux["real_func_cnt"])
  69. for i in range(1, real_func_cnt):
  70. progs.append(func[i])
  71. return progs
  72. class ProgAddBreakpoint(gdb.Breakpoint):
  73. def __init__(self, monitor):
  74. super(ProgAddBreakpoint, self).__init__("bpf_prog_kallsyms_add",
  75. internal=True)
  76. self.silent = True
  77. self.monitor = monitor
  78. def stop(self):
  79. self.monitor.add(gdb.parse_and_eval("fp"))
  80. return False
  81. class ProgRemoveBreakpoint(gdb.Breakpoint):
  82. def __init__(self, monitor):
  83. super(ProgRemoveBreakpoint, self).__init__("bpf_prog_free_id",
  84. internal=True)
  85. self.silent = True
  86. self.monitor = monitor
  87. def stop(self):
  88. self.monitor.remove(gdb.parse_and_eval("prog"))
  89. return False
  90. class ProgMonitor:
  91. def __init__(self, add, remove):
  92. self.add = add
  93. self.remove = remove
  94. self.add_bp = ProgAddBreakpoint(self)
  95. self.remove_bp = ProgRemoveBreakpoint(self)
  96. self.notify_initial()
  97. def notify_initial(self):
  98. for prog in list_progs():
  99. self.add(prog)
  100. def delete(self):
  101. self.add_bp.delete()
  102. self.remove_bp.delete()
  103. def btf_str_by_offset(btf, offset):
  104. while offset < btf["start_str_off"]:
  105. btf = btf["base_btf"]
  106. offset -= btf["start_str_off"]
  107. if offset < btf["hdr"]["str_len"]:
  108. return (btf["strings"] + offset).string()
  109. return None
  110. def bpf_line_info_line_num(line_col):
  111. return line_col >> 10
  112. def bpf_line_info_line_col(line_col):
  113. return line_col & 0x3ff
  114. class LInfoIter:
  115. def __init__(self, prog):
  116. # See bpf_prog_get_file_line() for details.
  117. self.pos = 0
  118. self.nr_linfo = 0
  119. if prog is None:
  120. return
  121. self.bpf_func = int(prog["bpf_func"])
  122. aux = prog["aux"]
  123. self.btf = aux["btf"]
  124. linfo_idx = aux["linfo_idx"]
  125. self.nr_linfo = int(aux["nr_linfo"]) - linfo_idx
  126. if self.nr_linfo == 0:
  127. return
  128. linfo_ptr = aux["linfo"]
  129. tpe = linfo_ptr.type.target().array(self.nr_linfo).pointer()
  130. self.linfo = (linfo_ptr + linfo_idx).cast(tpe).dereference()
  131. jited_linfo_ptr = aux["jited_linfo"]
  132. tpe = jited_linfo_ptr.type.target().array(self.nr_linfo).pointer()
  133. self.jited_linfo = (jited_linfo_ptr + linfo_idx).cast(tpe).dereference()
  134. self.filenos = {}
  135. def get_code_off(self):
  136. if self.pos >= self.nr_linfo:
  137. return -1
  138. return self.jited_linfo[self.pos] - self.bpf_func
  139. def advance(self):
  140. self.pos += 1
  141. def get_fileno(self):
  142. file_name_off = int(self.linfo[self.pos]["file_name_off"])
  143. fileno = self.filenos.get(file_name_off)
  144. if fileno is not None:
  145. return fileno, None
  146. file_name = btf_str_by_offset(self.btf, file_name_off)
  147. fileno = len(self.filenos) + 1
  148. self.filenos[file_name_off] = fileno
  149. return fileno, file_name
  150. def get_line_col(self):
  151. line_col = int(self.linfo[self.pos]["line_col"])
  152. return bpf_line_info_line_num(line_col), \
  153. bpf_line_info_line_col(line_col)
  154. def generate_debug_obj(ksym, prog):
  155. name = get_ksym_name(ksym)
  156. # Avoid read_memory(); it throws bogus gdb.MemoryError in some contexts.
  157. start = ksym["start"]
  158. code = start.cast(gdb.lookup_type("unsigned char")
  159. .array(int(ksym["end"]) - int(start))
  160. .pointer()).dereference().bytes
  161. linfo_iter = LInfoIter(prog)
  162. result = tempfile.NamedTemporaryFile(suffix=".o", mode="wb")
  163. try:
  164. with tempfile.NamedTemporaryFile(suffix=".s", mode="w") as src:
  165. # ".loc" does not apply to ".byte"s, only to ".insn"s, but since
  166. # this needs to work for all architectures, the latter are not an
  167. # option. Ask the assembler to apply ".loc"s to labels as well,
  168. # and generate dummy labels after each ".loc".
  169. src.write(".loc_mark_labels 1\n")
  170. src.write(".globl {}\n".format(name))
  171. src.write(".type {},@function\n".format(name))
  172. src.write("{}:\n".format(name))
  173. for code_off, code_byte in enumerate(code):
  174. if linfo_iter.get_code_off() == code_off:
  175. fileno, file_name = linfo_iter.get_fileno()
  176. if file_name is not None:
  177. src.write(".file {} {}\n".format(
  178. fileno, json.dumps(file_name)))
  179. line, col = linfo_iter.get_line_col()
  180. src.write(".loc {} {} {}\n".format(fileno, line, col))
  181. src.write("0:\n")
  182. linfo_iter.advance()
  183. src.write(".byte {}\n".format(code_byte))
  184. src.write(".size {},{}\n".format(name, len(code)))
  185. src.flush()
  186. try:
  187. subprocess.check_call(["as", "-c", src.name, "-o", result.name])
  188. except FileNotFoundError:
  189. # "as" is not installed.
  190. result.close()
  191. return None
  192. return result
  193. except:
  194. result.close()
  195. raise