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