1# Copyright 2019 Clevernet 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from os import linesep 16import ctypes as ct 17from .table import get_table_type_name 18 19class OffsetUnion(ct.Union): 20 _fields_ = [('offsetu', ct.c_uint16), ('offset', ct.c_int16)] 21 22class ImmUnion(ct.Union): 23 _fields_ = [('immu', ct.c_uint32), ('imm', ct.c_int32)] 24 25class BPFInstrFields(ct.Structure): 26 _pack_ = 1 27 _anonymous_ = ('o', 'i') 28 _fields_ = [('opcode', ct.c_uint8), 29 ('dst', ct.c_uint8, 4), 30 ('src', ct.c_uint8, 4), 31 ('o', OffsetUnion), 32 ('i', ImmUnion)] 33 34class BPFInstr(ct.Union): 35 _pack_ = 1 36 _anonymous_ = ('s') 37 _fields_ = [('s', BPFInstrFields), ('instr', ct.c_uint64)] 38 39class BPFDecoder(): 40 BPF_PSEUDO_CALL = 1 41 bpf_helpers = ['unspec', 42 'map_lookup_elem', 43 'map_update_elem', 44 'map_delete_elem', 45 'probe_read', 46 'ktime_get_ns', 47 'trace_printk', 48 'get_prandom_u32', 49 'get_smp_processor_id', 50 'skb_store_bytes', 51 'l3_csum_replace', 52 'l4_csum_replace', 53 'tail_call', 54 'clone_redirect', 55 'get_current_pid_tgid', 56 'get_current_uid_gid', 57 'get_current_comm', 58 'get_cgroup_classid', 59 'skb_vlan_push', 60 'skb_vlan_pop', 61 'skb_get_tunnel_key', 62 'skb_set_tunnel_key', 63 'perf_event_read', 64 'redirect', 65 'get_route_realm', 66 'perf_event_output', 67 'skb_load_bytes', 68 'get_stackid', 69 'csum_diff', 70 'skb_get_tunnel_opt', 71 'skb_set_tunnel_opt', 72 'skb_change_proto', 73 'skb_change_type', 74 'skb_under_cgroup', 75 'get_hash_recalc', 76 'get_current_task', 77 'probe_write_user', 78 'current_task_under_cgroup', 79 'skb_change_tail', 80 'skb_pull_data', 81 'csum_update', 82 'set_hash_invalid', 83 'get_numa_node_id', 84 'skb_change_head', 85 'xdp_adjust_head', 86 'probe_read_str', 87 'get_socket_cookie', 88 'get_socket_uid', 89 'set_hash', 90 'setsockopt', 91 'skb_adjust_room', 92 'redirect_map', 93 'sk_redirect_map', 94 'sock_map_update', 95 'xdp_adjust_meta', 96 'perf_event_read_value', 97 'perf_prog_read_value', 98 'getsockopt', 99 'override_return', 100 'sock_ops_cb_flags_set', 101 'msg_redirect_map', 102 'msg_apply_bytes', 103 'msg_cork_bytes', 104 'msg_pull_data', 105 'bind', 106 'xdp_adjust_tail', 107 'skb_get_xfrm_state', 108 'get_stack', 109 'skb_load_bytes_relative', 110 'fib_lookup', 111 'sock_hash_update', 112 'msg_redirect_hash', 113 'sk_redirect_hash', 114 'lwt_push_encap', 115 'lwt_seg6_store_bytes', 116 'lwt_seg6_adjust_srh', 117 'lwt_seg6_action', 118 'rc_repeat', 119 'rc_keydown', 120 'skb_cgroup_id', 121 'get_current_cgroup_id', 122 'get_local_storage', 123 'sk_select_reuseport', 124 'skb_ancestor_cgroup_id', 125 'sk_lookup_tcp', 126 'sk_lookup_udp', 127 'sk_release', 128 'map_push_elem', 129 'map_pop_elem', 130 'map_peek_elem', 131 'msg_push_data', 132 'msg_pop_data', 133 'rc_pointer_rel'] 134 135 opcodes = {0x04: ('add32', 'dstimm', '+=', 32), 136 0x05: ('ja', 'joff', None, 64), 137 0x07: ('add', 'dstimm', '+=', 64), 138 0x0c: ('add32', 'dstsrc', '+=', 32), 139 0x0f: ('add', 'dstsrc', '+=', 64), 140 0x14: ('sub32', 'dstimm', '-=', 32), 141 0x15: ('jeq', 'jdstimmoff', '==', 64), 142 0x17: ('sub', 'dstimm', '-=', 64), 143 0x18: ('lddw', 'lddw', None, 64), 144 0x1c: ('sub32', 'dstsrc', '-=', 32), 145 0x1d: ('jeq', 'jdstsrcoff', '==', 64), 146 0x1f: ('sub', 'dstsrc', '-=', 64), 147 0x20: ('ldabsw', 'ldabs', None, 32), 148 0x24: ('mul32', 'dstimm', '*=', 32), 149 0x25: ('jgt', 'jdstimmoff', '>', 64), 150 0x27: ('mul', 'dstimm', '*=', 64), 151 0x28: ('ldabsh', 'ldabs', None, 16), 152 0x2c: ('mul32', 'dstsrc', '*=', 32), 153 0x2d: ('jgt', 'jdstsrcoff', '>', 64), 154 0x2f: ('mul', 'dstsrc', '*=', 64), 155 0x30: ('ldabsb', 'ldabs', None, 8), 156 0x34: ('div32', 'dstimm', '/=', 32), 157 0x35: ('jge', 'jdstimmoff', '>=', 64), 158 0x37: ('div', 'dstimm', '/=', 64), 159 0x38: ('ldabsdw', 'ldabs', None, 64), 160 0x3c: ('div32', 'dstsrc', '/=', 32), 161 0x3d: ('jge', 'jdstsrcoff', '>=', 64), 162 0x3f: ('div', 'dstsrc', '/=', 64), 163 0x40: ('ldindw', 'ldind', None, 32), 164 0x44: ('or32', 'dstimm_bw', '|=', 32), 165 0x45: ('jset', 'jdstimmoff', '&', 64), 166 0x47: ('or', 'dstimm_bw', '|=', 64), 167 0x48: ('ldindh', 'ldind', None, 16), 168 0x4c: ('or32', 'dstsrc', '|=', 32), 169 0x4d: ('jset', 'jdstsrcoff', '&', 64), 170 0x4f: ('or', 'dstsrc', '|=', 64), 171 0x50: ('ldindb', 'ldind', None, 8), 172 0x54: ('and32', 'dstimm_bw', '&=', 32), 173 0x55: ('jne', 'jdstimmoff', '!=', 64), 174 0x57: ('and', 'dstimm_bw', '&=', 64), 175 0x58: ('ldinddw', 'ldind', None, 64), 176 0x5c: ('and32', 'dstsrc', '&=', 32), 177 0x5d: ('jne', 'jdstsrcoff', '!=', 64), 178 0x5f: ('and', 'dstsrc', '&=', 64), 179 0x61: ('ldxw', 'ldstsrcoff', None, 32), 180 0x62: ('stw', 'sdstoffimm', None, 32), 181 0x63: ('stxw', 'sdstoffsrc', None, 32), 182 0x64: ('lsh32', 'dstimm', '<<=', 32), 183 0x65: ('jsgt', 'jdstimmoff', 's>', 64), 184 0x67: ('lsh', 'dstimm', '<<=', 64), 185 0x69: ('ldxh', 'ldstsrcoff', None, 16), 186 0x6a: ('sth', 'sdstoffimm', None, 16), 187 0x6b: ('stxh', 'sdstoffsrc', None, 16), 188 0x6c: ('lsh32', 'dstsrc', '<<=', 32), 189 0x6d: ('jsgt', 'jdstsrcoff', 's>', 64), 190 0x6f: ('lsh', 'dstsrc', '<<=', 64), 191 0x71: ('ldxb', 'ldstsrcoff', None, 8), 192 0x72: ('stb', 'sdstoffimm', None, 8), 193 0x73: ('stxb', 'sdstoffsrc', None, 8), 194 0x74: ('rsh32', 'dstimm', '>>=', 32), 195 0x75: ('jsge', 'jdstimmoff', 's>=', 64), 196 0x77: ('rsh', 'dstimm', '>>=', 64), 197 0x79: ('ldxdw', 'ldstsrcoff', None, 64), 198 0x7a: ('stdw', 'sdstoffimm', None, 64), 199 0x7b: ('stxdw', 'sdstoffsrc', None, 64), 200 0x7c: ('rsh32', 'dstsrc', '>>=', 32), 201 0x7d: ('jsge', 'jdstsrcoff', 's>=', 64), 202 0x7f: ('rsh', 'dstsrc', '>>=', 64), 203 0x84: ('neg32', 'dst', '~', 32), 204 0x85: ('call', 'call', None, 64), 205 0x87: ('neg', 'dst', '~', 64), 206 0x94: ('mod32', 'dstimm', '%=', 32), 207 0x95: ('exit', 'exit', None, 64), 208 0x97: ('mod', 'dstimm', '%=', 64), 209 0x9c: ('mod32', 'dstsrc', '%=', 32), 210 0x9f: ('mod', 'dstsrc', '%=', 64), 211 0xa4: ('xor32', 'dstimm_bw', '^=', 32), 212 0xa5: ('jlt', 'jdstimmoff', '<', 64), 213 0xa7: ('xor', 'dstimm_bw', '^=', 64), 214 0xac: ('xor32', 'dstsrc', '^=', 32), 215 0xad: ('jlt', 'jdstsrcoff', '<', 64), 216 0xaf: ('xor', 'dstsrc', '^=', 64), 217 0xb4: ('mov32', 'dstimm', '=', 32), 218 0xb5: ('jle', 'jdstimmoff', '<=', 64), 219 0xb7: ('mov', 'dstimm', '=', 64), 220 0xbc: ('mov32', 'dstsrc', '=', 32), 221 0xbd: ('jle', 'jdstsrcoff', '<=', 64), 222 0xbf: ('mov', 'dstsrc', '=', 64), 223 0xc4: ('arsh32', 'dstimm', 's>>=', 32), 224 0xc5: ('jslt', 'jdstimmoff', 's<', 64), 225 0xc7: ('arsh', 'dstimm', 's>>=', 64), 226 0xcc: ('arsh32', 'dstsrc', 's>>=', 32), 227 0xcd: ('jslt', 'jdstsrcoff', 's<', 64), 228 0xcf: ('arsh', 'dstsrc', 's>>=', 64), 229 0xd5: ('jsle', 'jdstimmoff', 's<=', 64), 230 0xdc: ('endian32', 'dstsrc', 'endian', 32), 231 0xdd: ('jsle', 'jdstimmoff', 's<=', 64),} 232 233 @classmethod 234 def decode(cls, i, w, w1): 235 try: 236 name, opclass, op, bits = cls.opcodes[w.opcode] 237 if opclass == 'dstimm': 238 return 'r%d %s %d' % (w.dst, op, w.imm), 0 239 240 elif opclass == 'dstimm_bw': 241 return 'r%d %s 0x%x' % (w.dst, op, w.immu), 0 242 243 elif opclass == 'joff': 244 return 'goto %s <%d>' % ('%+d' % (w.offset), 245 i + w.offset + 1), 0 246 247 elif opclass == 'dstsrc': 248 return 'r%d %s r%d' % (w.dst, op, w.src), 0 249 250 elif opclass == 'jdstimmoff': 251 return 'if r%d %s %d goto pc%s <%d>' % (w.dst, op, w.imm, 252 '%+d' % (w.offset), 253 i + w.offset + 1), 0 254 255 elif opclass == 'jdstsrcoff': 256 return 'if r%d %s r%d goto pc%s <%d>' % (w.dst, op, w.src, 257 '%+d' % (w.offset), 258 i + w.offset + 1), 0 259 260 elif opclass == 'lddw': 261 # imm contains the file descriptor (FD) of the map being loaded; 262 # the kernel will translate this into the proper address 263 if w1 is None: 264 raise Exception("lddw requires two instructions to be disassembled") 265 if w1.imm == 0: 266 return 'r%d = <map at fd #%d>' % (w.dst, w.imm), 1 267 imm = (w1.imm << 32) | w.imm 268 return 'r%d = 0x%x' % (w.dst, imm), 1 269 270 elif opclass == 'ldabs': 271 return 'r0 = *(u%s*)skb[%s]' % (bits, w.imm), 0 272 273 elif opclass == 'ldind': 274 return 'r0 = *(u%d*)skb[r%d %s]' % (bits, w.src, 275 '%+d' % (w.imm)), 0 276 277 elif opclass == 'ldstsrcoff': 278 return 'r%d = *(u%d*)(r%d %s)' % (w.dst, bits, w.src, 279 '%+d' % (w.offset)), 0 280 281 elif opclass == 'sdstoffimm': 282 return '*(u%d*)(r%d %s) = %d' % (bits, w.dst, 283 '%+d' % (w.offset), w.imm), 0 284 285 elif opclass == 'sdstoffsrc': 286 return '*(u%d*)(r%d %s) = r%d' % (bits, w.dst, 287 '%+d' % (w.offset), w.src), 0 288 289 elif opclass == 'dst': 290 return 'r%d = %s (u%s)r%d' % (w.dst, op, bits, w.dst), 0 291 292 elif opclass == 'call': 293 if w.src != cls.BPF_PSEUDO_CALL: 294 try: 295 return '%s bpf_%s#%d' % (name, cls.bpf_helpers[w.immu], w.immu), 0 296 except IndexError: 297 return '%s <unknown helper #%d>' % (op, w.immu), 0 298 return '%s %s' % (name, '%+d' % (w.imm)), 0 299 elif opclass == 'exit': 300 return name, 0 301 else: 302 raise Exception('unknown opcode class') 303 304 except KeyError: 305 return 'unknown <0x%x>' % (w.opcode) 306 307def disassemble_instruction(i, w0, w1=None): 308 instr, skip = BPFDecoder.decode(i, w0, w1) 309 return "%4d: (%02x) %s" % (i, w0.opcode, instr), skip 310 311def disassemble_str(bpfstr): 312 ptr = ct.cast(ct.c_char_p(bpfstr), ct.POINTER(BPFInstr)) 313 numinstr = int(len(bpfstr) / 8) 314 w0 = ptr[0] 315 skip = 0 316 instr_list = [] 317 for i in range(1, numinstr): 318 w1 = ptr[i] 319 if skip: 320 skip -= 1 321 instr_str = "%4d: (64-bit upper word)" % (i) 322 else: 323 instr_str, skip = disassemble_instruction(i - 1, w0, w1) 324 instr_list.append(instr_str) 325 w0 = w1 326 instr_str, skip = disassemble_instruction(numinstr - 1, w0, None) 327 instr_list.append(instr_str) 328 return instr_list 329 330def disassemble_prog(func_name, bpfstr): 331 instr_list = ["Disassemble of BPF program %s:" % (func_name)] 332 instr_list += disassemble_str(bpfstr) 333 return linesep.join(instr_list) 334 335class MapDecoder (): 336 ctype2str = {ct.c_bool: u"_Bool", 337 ct.c_char: u"char", 338 ct.c_wchar: u"wchar_t", 339 ct.c_ubyte: u"unsigned char", 340 ct.c_short: u"short", 341 ct.c_ushort: u"unsigned short", 342 ct.c_int: u"int", 343 ct.c_uint: u"unsigned int", 344 ct.c_long: u"long", 345 ct.c_ulong: u"unsigned long", 346 ct.c_longlong: u"long long", 347 ct.c_ulonglong: u"unsigned long long", 348 ct.c_float: u"float", 349 ct.c_double: u"double", 350 ct.c_longdouble: u"long double", 351 ct.c_int64 * 2: u"__int128", 352 ct.c_uint64 * 2: u"unsigned __int128",} 353 354 @classmethod 355 def get_ct_name(cls, t): 356 try: 357 if issubclass(t, ct.Structure): 358 field_type_name = "struct" 359 elif issubclass(t, ct.Union): 360 field_type_name = "union" 361 elif issubclass(t, ct.Array): 362 field_type_name = cls.ctype2str[t._type_] + "[" + str(t._length_) + "]" 363 else: 364 field_type_name = cls.ctype2str[t] 365 except KeyError: 366 field_type_name = str(t) 367 return field_type_name 368 369 @classmethod 370 def format_size_info(cls, offset, size, enabled=False, bitoffset=None): 371 if not enabled: 372 return "" 373 if bitoffset is not None: 374 return "[%d,%d +%d bit]" % (offset, bitoffset, size) 375 return "[%d +%d] " % (offset, size) 376 377 @classmethod 378 def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): 379 map_lines = [] 380 try: 381 for field_name, field_type in t._fields_: 382 is_structured = (issubclass(field_type, ct.Structure) or 383 issubclass(field_type, ct.Union)) 384 field_type_name = cls.get_ct_name(field_type) 385 field_offset = getattr(t, field_name).offset 386 field_size = ct.sizeof(field_type) 387 sizedesc = cls.format_size_info(offset + field_offset, 388 field_size, sizeinfo) 389 if is_structured: 390 map_lines.append("%s%s%s {" % (indent, sizedesc, field_type_name)) 391 map_lines += cls.print_ct_map(field_type, 392 indent + " ", 393 offset + field_offset) 394 map_lines.append("%s} %s;" % (indent, field_name)) 395 else: 396 map_lines.append("%s%s%s %s;" % (indent, sizedesc, 397 field_type_name, 398 field_name)) 399 except ValueError: 400 # is a bit field 401 offset_bits = 0 402 for field in t._fields_: 403 if len(field) == 3: 404 field_name, field_type, field_bits = field 405 field_type_name = cls.get_ct_name(field_type) 406 sizedesc = cls.format_size_info(offset, offset_bits, 407 sizeinfo, field_bits) 408 map_lines.append("%s%s%s %s:%d;" % (indent, sizedesc, 409 field_type_name, 410 field_name, 411 field_bits)) 412 else: 413 # end of previous bit field 414 field_name, field_type = field 415 field_type_name = cls.get_ct_name(field_type) 416 field_offset = getattr(t, field_name).offset 417 field_size = ct.sizeof(field_type) 418 field_bits = 0 419 offset_bits = 0 420 sizedesc = cls.format_size_info(offset + field_offset, 421 field_size, sizeinfo) 422 map_lines.append("%s%s%s %s;" % (indent, sizedesc, 423 field_type_name, 424 field_name)) 425 offset += field_offset 426 offset_bits += field_bits 427 return map_lines 428 429 @classmethod 430 def print_map_ctype(cls, t, field_name, sizeinfo): 431 is_structured = (issubclass(t, ct.Structure) or 432 issubclass(t, ct.Union)) 433 type_name = cls.get_ct_name(t) 434 if is_structured: 435 map_lines = [" %s {" % (type_name)] 436 map_lines += cls.print_ct_map(t, " ", sizeinfo=sizeinfo) 437 map_lines.append(" } %s;" % (field_name)) 438 else: 439 map_lines = [" %s %s;" % (type_name, field_name)] 440 return map_lines 441 442 @classmethod 443 def decode_map(cls, map_name, map_obj, map_type, sizeinfo=False): 444 map_lines = ['Layout of BPF map %s (type %s, FD %d, ID %d):' % (map_name, 445 map_type, 446 map_obj.map_fd, 447 map_obj.map_id)] 448 map_lines += cls.print_map_ctype(map_obj.Key, 'key', sizeinfo=sizeinfo) 449 map_lines += cls.print_map_ctype(map_obj.Leaf, 'value', sizeinfo=sizeinfo) 450 return linesep.join(map_lines) 451 452def decode_map(map_name, map_obj, map_type, sizeinfo=False): 453 map_type_name = get_table_type_name(map_type) 454 return MapDecoder.decode_map(map_name, map_obj, map_type_name, sizeinfo=sizeinfo) 455