From 40ad17d582a56e4b59cb9c3e9ccac59f08e43c4a Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Fri, 15 Feb 2019 22:07:44 +0100 Subject: [PATCH 1/7] Python BPF disassembler and map layout parser Debugging eBPF programs can be tricky. The clang debug flags are not supported in all the code-loading branches yet - e.g., only load_prog() supports BPF_DEBUG or DEBUG_BPF_REGISTER_STATE, but compiling a kprobe with BPF(...) doesn't. This built-in disassembler can disassemble and print the BPF code in a similar syntax than the kernel, whenever and the number of times the user needs it. The BPF ISA is relatively stable so it doesn't require much maintenance. In addition, this parser is agnostic from the original source language (C, B, Go, etc.), and doesn't depend on a particular compiler. Example output for trace_pid_start() in biotop: Disassemble of BPF program trace_pid_start: 0: (79) r1 = *(u64*)(r1 +112) 1: (7b) *(u64*)(r10 -8) = r1 2: (b7) r1 = 0 3: (63) *(u32*)(r10 -16) = r1 4: (7b) *(u64*)(r10 -24) = r1 5: (7b) *(u64*)(r10 -32) = r1 6: (bf) r1 = r10 7: (07) r1 += -28 8: (b7) r2 = 16 9: (85) call bpf_get_current_comm#16 10: (67) r0 <<= 32 11: (77) r0 >>= 32 12: (55) if r0 != 0 goto +10 <23> 13: (85) call bpf_get_current_pid_tgid#14 14: (63) *(u32*)(r10 -32) = r0 15: (18) r1 = 17: (64-bit upper word) 17: (bf) r2 = r10 18: (07) r2 += -8 19: (bf) r3 = r10 20: (07) r3 += -32 21: (b7) r4 = 0 22: (85) call bpf_map_update_elem#2 23: (b7) r0 = 0 24: (95) exit The fields, types and memory layouts of maps can also be printed, which is something that can be really helpful when dealing with unaligned accesses or packed vs unpacked structures, and currently not supported by clang. For a map with key: struct {int a; short b; struct {int c:4; int d:8;};}); and value u64 the example output is: Layout of BPF type HASH map test_map (ID 0): struct { [0 +4] int a; [4 +2] short b; [6 +2] char[2] __pad_2; [8 +4] struct { int c:4; int d:8; } __anon0; } key; unsigned long long value; The [X +Y] is optional and denotes the offset and the size of each field. Note that bit-fields and padding fields are shown. --- src/python/bcc/__init__.py | 10 + src/python/bcc/disassembler.py | 441 +++++++++++++++++++++++++++++++++ src/python/bcc/table.py | 26 ++ 3 files changed, 477 insertions(+) create mode 100644 src/python/bcc/disassembler.py diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 2c45d8c6dd26..900de5b89c01 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -30,6 +30,7 @@ from .syscall import syscall_name from .utils import get_online_cpus, printb, _assert_is_bytes, ArgString from .version import __version__ +from .disassembler import disassemble_prog, decode_map _probe_limit = 1000 _num_open_probes = 0 @@ -400,6 +401,15 @@ def dump_func(self, func_name): size, = lib.bpf_function_size(self.module, func_name), return ct.string_at(start, size) + def disassemble_func(self, func_name): + bpfstr = self.dump_func(func_name) + disassemble_prog(func_name, bpfstr) + + def decode_table(self, table_name, sizeinfo=False): + table_obj = self[table_name] + table_type = lib.bpf_table_type_id(self.module, table_obj.map_id) + decode_map(table_name, table_obj, table_type, sizeinfo=sizeinfo) + str2ctype = { u"_Bool": ct.c_bool, u"char": ct.c_char, diff --git a/src/python/bcc/disassembler.py b/src/python/bcc/disassembler.py new file mode 100644 index 000000000000..c1d117e76d0a --- /dev/null +++ b/src/python/bcc/disassembler.py @@ -0,0 +1,441 @@ +# Copyright 2019 Clevernet +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ctypes as ct +from .table import get_table_type_name +from .libbcc import lib + +class OffsetUnion(ct.Union): + _fields_ = [('offsetu', ct.c_uint16), ('offset', ct.c_int16)] + +class ImmUnion(ct.Union): + _fields_ = [('immu', ct.c_uint32), ('imm', ct.c_int32)] + +class BPFInstrFields(ct.Structure): + _pack_ = 1 + _anonymous_ = ('o', 'i') + _fields_ = [('opcode', ct.c_uint8), + ('dst', ct.c_uint8, 4), + ('src', ct.c_uint8, 4), + ('o', OffsetUnion), + ('i', ImmUnion)] + +class BPFInstr(ct.Union): + _pack_ = 1 + _anonymous_ = ('s') + _fields_ = [('s', BPFInstrFields), ('instr', ct.c_uint64)] + +class BPFDecoder(): + BPF_PSEUDO_CALL = 1 + bpf_helpers = ['unspec', + 'map_lookup_elem', + 'map_update_elem', + 'map_delete_elem', + 'probe_read', + 'ktime_get_ns', + 'trace_printk', + 'get_prandom_u32', + 'get_smp_processor_id', + 'skb_store_bytes', + 'l3_csum_replace', + 'l4_csum_replace', + 'tail_call', + 'clone_redirect', + 'get_current_pid_tgid', + 'get_current_uid_gid', + 'get_current_comm', + 'get_cgroup_classid', + 'skb_vlan_push', + 'skb_vlan_pop', + 'skb_get_tunnel_key', + 'skb_set_tunnel_key', + 'perf_event_read', + 'redirect', + 'get_route_realm', + 'perf_event_output', + 'skb_load_bytes', + 'get_stackid', + 'csum_diff', + 'skb_get_tunnel_opt', + 'skb_set_tunnel_opt', + 'skb_change_proto', + 'skb_change_type', + 'skb_under_cgroup', + 'get_hash_recalc', + 'get_current_task', + 'probe_write_user', + 'current_task_under_cgroup', + 'skb_change_tail', + 'skb_pull_data', + 'csum_update', + 'set_hash_invalid', + 'get_numa_node_id', + 'skb_change_head', + 'xdp_adjust_head', + 'probe_read_str', + 'get_socket_cookie', + 'get_socket_uid', + 'set_hash', + 'setsockopt', + 'skb_adjust_room', + 'redirect_map', + 'sk_redirect_map', + 'sock_map_update', + 'xdp_adjust_meta', + 'perf_event_read_value', + 'perf_prog_read_value', + 'getsockopt', + 'override_return', + 'sock_ops_cb_flags_set', + 'msg_redirect_map', + 'msg_apply_bytes', + 'msg_cork_bytes', + 'msg_pull_data', + 'bind', + 'xdp_adjust_tail', + 'skb_get_xfrm_state', + 'get_stack', + 'skb_load_bytes_relative', + 'fib_lookup', + 'sock_hash_update', + 'msg_redirect_hash', + 'sk_redirect_hash', + 'lwt_push_encap', + 'lwt_seg6_store_bytes', + 'lwt_seg6_adjust_srh', + 'lwt_seg6_action', + 'rc_repeat', + 'rc_keydown', + 'skb_cgroup_id', + 'get_current_cgroup_id', + 'get_local_storage', + 'sk_select_reuseport', + 'skb_ancestor_cgroup_id', + 'sk_lookup_tcp', + 'sk_lookup_udp', + 'sk_release', + 'map_push_elem', + 'map_pop_elem', + 'map_peek_elem', + 'msg_push_data', + 'msg_pop_data', + 'rc_pointer_rel'] + + opcodes = {0x04: ('add32', 'dstimm', '+=', 32), + 0x05: ('ja', 'joff', None, 64), + 0x07: ('add', 'dstimm', '+=', 64), + 0x0c: ('add32', 'dstsrc', '+=', 32), + 0x0f: ('add', 'dstsrc', '+=', 64), + 0x14: ('sub32', 'dstimm', '-=', 32), + 0x15: ('jeq', 'jdstimmoff', '==', 64), + 0x17: ('sub', 'dstimm', '-=', 64), + 0x18: ('lddw', 'lddw', None, 64), + 0x1c: ('sub32', 'dstsrc', '-=', 32), + 0x1d: ('jeq', 'jdstsrcoff', '>=', 64), + 0x1f: ('sub', 'dstsrc', '-=', 64), + 0x20: ('ldabsw', 'ldabs', None, 32), + 0x24: ('mul32', 'dstimm', '*=', 32), + 0x25: ('jgt', 'jdstimmoff', '>', 64), + 0x27: ('mul', 'dstimm', '*=', 64), + 0x28: ('ldabsh', 'ldabs', None, 16), + 0x2c: ('mul32', 'dstsrc', '*=', 32), + 0x2d: ('jgt', 'jdstsrcoff', '>', 64), + 0x2f: ('mul', 'dstsrc', '*=', 64), + 0x30: ('ldabsb', 'ldabs', None, 8), + 0x34: ('div32', 'dstimm', '/=', 32), + 0x35: ('jge', 'jdstimmoff', '>=', 64), + 0x37: ('div', 'dstimm', '/=', 64), + 0x38: ('ldabsdw', 'ldabs', None, 32), + 0x3c: ('div32', 'dstsrc', '/=', 32), + 0x3d: ('jge', 'jdstsrcoff', '>=', 64), + 0x3f: ('div', 'dstsrc', '/=', 64), + 0x40: ('ldindw', 'ldind', None, 32), + 0x44: ('or32', 'dstimm_bw', '|=', 32), + 0x45: ('jset', 'jdstimmoff', '&', 64), + 0x47: ('or', 'dstimm_bw', '|=', 64), + 0x48: ('ldindh', 'ldind', None, 16), + 0x4c: ('or32', 'dstsrc', '|=', 32), + 0x4d: ('jset', 'jdstsrcoff', '&', 64), + 0x4f: ('or', 'dstsrc', '|=', 64), + 0x50: ('ldindb', 'ldind', None, 8), + 0x54: ('and32', 'dstimm_bw', '&=', 32), + 0x55: ('jne', 'jdstimmoff', '!=', 64), + 0x57: ('and', 'dstimm_bw', '&=', 64), + 0x58: ('ldinddw', 'ldind', None, 64), + 0x5c: ('and32', 'dstsrc', '&=', 32), + 0x5d: ('jne', 'jdstsrcoff', '!=', 64), + 0x5f: ('and', 'dstsrc', '&=', 64), + 0x61: ('ldxw', 'ldstsrcoff', None, 32), + 0x62: ('stw', 'sdstoffimm', None, 32), + 0x63: ('stxw', 'sdstoffsrc', None, 32), + 0x64: ('lsh32', 'dstimm', '<<=', 32), + 0x65: ('jsgt', 'jdstimmoff', 's>', 64), + 0x67: ('lsh', 'dstimm', '<<=', 64), + 0x69: ('ldxh', 'ldstsrcoff', None, 16), + 0x6a: ('sth', 'sdstoffimm', None, 16), + 0x6b: ('stxh', 'sdstoffsrc', None, 16), + 0x6c: ('lsh32', 'dstsrc', '<<=', 32), + 0x6d: ('jsgt', 'jdstsrcoff', 's>', 64), + 0x6f: ('lsh', 'dstsrc', '<<=', 64), + 0x71: ('ldxb', 'ldstsrcoff', None, 8), + 0x72: ('stb', 'sdstoffimm', None, 8), + 0x73: ('stxb', 'sdstoffsrc', None, 8), + 0x74: ('rsh32', 'dstimm', '>>=', 32), + 0x75: ('jsge', 'jdstimmoff', 's>=', 64), + 0x77: ('rsh', 'dstimm', '>>=', 64), + 0x79: ('ldxdw', 'ldstsrcoff', None, 64), + 0x7a: ('stdw', 'sdstoffimm', None, 64), + 0x7b: ('stxdw', 'sdstoffsrc', None, 64), + 0x7c: ('rsh32', 'dstsrc', '>>=', 32), + 0x7d: ('jsge', 'jdstsrcoff', 's>=', 64), + 0x7f: ('rsh', 'dstsrc', '>>=', 64), + 0x84: ('neg32', 'dst', '~', 32), + 0x85: ('call', 'call', None, 64), + 0x87: ('neg', 'dst', '~', 64), + 0x94: ('mod32', 'dstimm', '%=', 32), + 0x95: ('exit', 'exit', None, 64), + 0x97: ('mod', 'dstimm', '%=', 64), + 0x9c: ('mod32', 'dstsrc', '%=', 32), + 0x9f: ('mod', 'dstsrc', '%=', 64), + 0xa4: ('xor32', 'dstimm_bw', '^=', 32), + 0xa5: ('jlt', 'jdstimmoff', '<', 64), + 0xa7: ('xor', 'dstimm_bw', '^=', 64), + 0xac: ('xor32', 'dstsrc', '^=', 32), + 0xad: ('jlt', 'jdstsrcoff', '<', 64), + 0xaf: ('xor', 'dstsrc', '^=', 64), + 0xb4: ('mov32', 'dstimm', '=', 32), + 0xb5: ('jle', 'jdstimmoff', '<=', 64), + 0xb7: ('mov', 'dstimm', '=', 64), + 0xbc: ('mov32', 'dstsrc', '=', 32), + 0xbd: ('jle', 'jdstsrcoff', '<=', 64), + 0xbf: ('mov', 'dstsrc', '=', 64), + 0xc4: ('arsh32', 'dstimm', 's>>=', 32), + 0xc5: ('jslt', 'jdstimmoff', 's<', 64), + 0xc7: ('arsh', 'dstimm', 's>>=', 64), + 0xcc: ('arsh32', 'dstsrc', 's>>=', 32), + 0xcd: ('jslt', 'jdstsrcoff', 's<', 64), + 0xcf: ('arsh', 'dstsrc', 's>>=', 64), + 0xd5: ('jsle', 'jdstimmoff', 's<=', 64), + 0xdc: ('endian32', 'dstsrc', 'endian', 32), + 0xdd: ('jsle', 'jdstimmoff', 's<=', 64),} + + @classmethod + def decode_offset(cls, offset): + if offset >= 0: + return '+%d' % (offset) + else: + return '%d' % (offset) + + @classmethod + def decode(cls, i, w, w1): + try: + name, opclass, op, bits = cls.opcodes[w.opcode] + if opclass == 'dstimm': + return 'r%d %s %d' % (w.dst, op, w.imm), 0 + + elif opclass == 'dstimm_bw': + return 'r%d %s 0x%x' % (w.dst, op, w.immu), 0 + + elif opclass == 'joff': + return 'goto %s <%d>' % (cls.decode_offset(w.offset), + i + w.offset), 0 + + elif opclass == 'dstsrc': + return 'r%d %s r%d' % (w.dst, op, w.src), 0 + + elif opclass == 'jdstimmoff': + return 'if r%d %s %d goto %s <%d>' % (w.dst, op, w.imm, + cls.decode_offset(w.offset), + i + w.offset + 1), 0 + + elif opclass == 'jdstsrcoff': + return 'if r%d %s r%d goto %s <%d>' % (w.dst, op, w.src, + cls.decode_offset(w.offset), + i + w.offset + 1), 0 + + elif opclass == 'lddw': + # imm contains the file descriptor (FD) of the map being loaded; + # the kernel will translate this into the proper address + if w1.imm == 0: + return 'r%d = ' % (w.dst, w.imm), 1 + imm = (w1.imm << 32) | w.imm + return 'r%d = 0x%x' % (w.dst, imm), 1 + + elif opclass == 'ldabs': + return 'r0 = *(u%s*)skb[%s]' % (bits, w.imm), 0 + + elif opclass == 'ldind': + return 'r0 = *(u%d*)skb[r%d %s]' % (bits, w.src, + cls.decode_offset(w.imm)), 0 + + elif opclass == 'ldstsrcoff': + return 'r%d = *(u%d*)(r%d %s)' % (w.dst, bits, w.src, + cls.decode_offset(w.offset)), 0 + + elif opclass == 'sdstoffimm': + return '*(u%d*)(r%d %s) = %d' % (bits, w.dst, + cls.decode_offset(w.offset), w.imm), 0 + + elif opclass == 'sdstoffsrc': + return '*(u%d*)(r%d %s) = r%d' % (bits, w.dst, + cls.decode_offset(w.offset), w.src), 0 + + elif opclass == 'dst': + return 'r%d = %s (u%s)r%d' % (w.dst, op, bits, w.dst), 0 + + elif opclass == 'call': + if w.src != cls.BPF_PSEUDO_CALL: + try: + return '%s bpf_%s#%d' % (name, cls.bpf_helpers[w.immu], w.immu), 0 + except IndexError: + return '%s ' % (op, w.immu), 0 + return '%s %s' % (name, cls.decode_offset(w.imm)), 0 + elif opclass == 'exit': + return name, 0 + else: + raise Exception('unknown opcode class') + + except KeyError: + return 'unknown <0x%x>' % (w.opcode) + +def _print_instruction(i, w0, w1): + instr, skip = BPFDecoder.decode(i, w0, w1) + print("%4d: (%02x) %s" % (i, w0.opcode, instr)) + return skip + +def disassemble_prog(func_name, bpfstr): + ptr = ct.cast(ct.c_char_p(bpfstr), ct.POINTER(BPFInstr)) + numinstr = len(bpfstr) / 8 + w0 = ptr[0] + skip = 0 + print("Disassemble of BPF program %s:" % (func_name)) + for i in range(1, numinstr): + w1 = ptr[i] + if skip: + skip -= 1 + print("%4d: (64-bit upper word)" % (i)) + else: + skip = _print_instruction(i - 1, w0, w1) + w0 = w1 + _print_instruction(numinstr - 1, w0, None) + +class MapDecoder (): + ctype2str = {ct.c_bool: u"_Bool", + ct.c_char: u"char", + ct.c_wchar: u"wchar_t", + ct.c_ubyte: u"unsigned char", + ct.c_short: u"short", + ct.c_ushort: u"unsigned short", + ct.c_int: u"int", + ct.c_uint: u"unsigned int", + ct.c_long: u"long", + ct.c_ulong: u"unsigned long", + ct.c_longlong: u"long long", + ct.c_ulonglong: u"unsigned long long", + ct.c_float: u"float", + ct.c_double: u"double", + ct.c_longdouble: u"long double", + ct.c_int64 * 2: u"__int128", + ct.c_uint64 * 2: u"unsigned __int128",} + + @classmethod + def get_ct_name(cls, t): + try: + if issubclass(t, ct.Structure): + field_type_name = "struct" + elif issubclass(t, ct.Union): + field_type_name = "union" + elif issubclass(t, ct.Array): + field_type_name = cls.ctype2str[t._type_] + "[" + str(t._length_) + "]" + else: + field_type_name = cls.ctype2str[t] + except KeyError: + field_type_name = str(t) + return field_type_name + + @classmethod + def format_size_info(cls, offset, size, enabled=False, bitoffset=None): + if not enabled: + return "" + if bitoffset is not None: + return "[%d,%d +%d bit]" % (offset, bitoffset, size) + return "[%d +%d] " % (offset, size) + + @classmethod + def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): + try: + for field_name, field_type in t._fields_: + is_structured = (issubclass(field_type, ct.Structure) or + issubclass(field_type, ct.Union)) + field_type_name = cls.get_ct_name(field_type) + field_offset = getattr(t, field_name).offset + field_size = ct.sizeof(field_type) + sizedesc = cls.format_size_info(offset + field_offset, + field_size, sizeinfo) + if is_structured: + print("%s%s%s {" % (indent, sizedesc, field_type_name)) + cls.print_ct_map(field_type, indent + " ", offset + field_offset) + print("%s} %s;" % (indent, field_name)) + else: + print("%s%s%s %s;" % (indent, sizedesc, + field_type_name, field_name)) + except ValueError: + # is a bit field + offset_bits = 0 + for field in t._fields_: + if len(field) == 3: + field_name, field_type, field_bits = field + field_type_name = cls.get_ct_name(field_type) + sizedesc = cls.format_size_info(offset, offset_bits, + sizeinfo, field_bits) + print("%s%s%s %s:%d;" % (indent, sizedesc, field_type_name, + field_name, field_bits)) + else: + # end of previous bit field + field_name, field_type = field + field_type_name = cls.get_ct_name(field_type) + field_offset = getattr(t, field_name).offset + field_size = ct.sizeof(field_type) + field_bits = 0 + offset_bits = 0 + sizedesc = cls.format_size_info(offset + field_offset, + field_size, sizeinfo) + print("%s%s%s %s;" % (indent, sizedesc, + field_type_name, field_name)) + offset += field_offset + offset_bits += field_bits + + @classmethod + def print_map_ctype(cls, t, field_name, sizeinfo): + is_structured = (issubclass(t, ct.Structure) or + issubclass(t, ct.Union)) + type_name = cls.get_ct_name(t); + if is_structured: + print(" %s {" % (type_name)) + cls.print_ct_map(t, " ", sizeinfo=sizeinfo) + print(" } %s;" % (field_name)) + else: + print(" %s %s;" % (type_name, field_name)) + + + @classmethod + def decode_map(cls, map_name, map_obj, map_type, sizeinfo=False): + print('Layout of BPF type %s map %s (ID %d):' % (map_type, map_name, + map_obj.map_id)) + cls.print_map_ctype(map_obj.Key, 'key', sizeinfo=sizeinfo) + cls.print_map_ctype(map_obj.Leaf, 'value', sizeinfo=sizeinfo) + +def decode_map(map_name, map_obj, map_type, sizeinfo=False): + map_type_name = get_table_type_name(map_type) + MapDecoder.decode_map(map_name, map_obj, map_type_name, sizeinfo=sizeinfo) diff --git a/src/python/bcc/table.py b/src/python/bcc/table.py index f2462570aa8e..d33d46eb017d 100644 --- a/src/python/bcc/table.py +++ b/src/python/bcc/table.py @@ -45,6 +45,25 @@ BPF_MAP_TYPE_XSKMAP = 17 BPF_MAP_TYPE_SOCKHASH = 18 +map_type_name = {BPF_MAP_TYPE_HASH: "HASH", + BPF_MAP_TYPE_ARRAY: "ARRAY", + BPF_MAP_TYPE_PROG_ARRAY: "PROG_ARRAY", + BPF_MAP_TYPE_PERF_EVENT_ARRAY: "PERF_EVENT_ARRAY", + BPF_MAP_TYPE_PERCPU_HASH: "PERCPU_HASH", + BPF_MAP_TYPE_PERCPU_ARRAY: "PERCPU_ARRAY", + BPF_MAP_TYPE_STACK_TRACE: "STACK_TRACE", + BPF_MAP_TYPE_CGROUP_ARRAY: "CGROUP_ARRAY", + BPF_MAP_TYPE_LRU_HASH: "LRU_HASH", + BPF_MAP_TYPE_LRU_PERCPU_HASH: "LRU_PERCPU_HASH", + BPF_MAP_TYPE_LPM_TRIE: "LPM_TRIE", + BPF_MAP_TYPE_ARRAY_OF_MAPS: "ARRAY_OF_MAPS", + BPF_MAP_TYPE_HASH_OF_MAPS: "HASH_OF_MAPS", + BPF_MAP_TYPE_DEVMAP: "DEVMAP", + BPF_MAP_TYPE_SOCKMAP: "SOCKMAP", + BPF_MAP_TYPE_CPUMAP: "CPUMAP", + BPF_MAP_TYPE_XSKMAP: "XSKMAP", + BPF_MAP_TYPE_SOCKHASH: "SOCKHASH",} + stars_max = 40 log2_index_max = 65 linear_index_max = 1025 @@ -123,6 +142,13 @@ def _print_linear_hist(vals, val_type): _stars(val, val_max, stars))) +def get_table_type_name(ttype): + try: + return map_type_name[ttype] + except KeyError: + return "" + + def Table(bpf, map_id, map_fd, keytype, leaftype, name, **kwargs): """Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs) From a717bd92a90428c385d0059261b3b4620f8c11bf Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Mon, 18 Feb 2019 12:42:31 +0100 Subject: [PATCH 2/7] Disassm: return strings instead of printing Signed-off-by: Oriol Arcas --- src/python/bcc/__init__.py | 4 +- src/python/bcc/disassembler.py | 110 +++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 900de5b89c01..3dc15f58b8e2 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -403,12 +403,12 @@ def dump_func(self, func_name): def disassemble_func(self, func_name): bpfstr = self.dump_func(func_name) - disassemble_prog(func_name, bpfstr) + return disassemble_prog(func_name, bpfstr) def decode_table(self, table_name, sizeinfo=False): table_obj = self[table_name] table_type = lib.bpf_table_type_id(self.module, table_obj.map_id) - decode_map(table_name, table_obj, table_type, sizeinfo=sizeinfo) + return decode_map(table_name, table_obj, table_type, sizeinfo=sizeinfo) str2ctype = { u"_Bool": ct.c_bool, diff --git a/src/python/bcc/disassembler.py b/src/python/bcc/disassembler.py index c1d117e76d0a..e691fb444d7d 100644 --- a/src/python/bcc/disassembler.py +++ b/src/python/bcc/disassembler.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from os import linesep import ctypes as ct from .table import get_table_type_name from .libbcc import lib @@ -142,7 +143,7 @@ class BPFDecoder(): 0x17: ('sub', 'dstimm', '-=', 64), 0x18: ('lddw', 'lddw', None, 64), 0x1c: ('sub32', 'dstsrc', '-=', 32), - 0x1d: ('jeq', 'jdstsrcoff', '>=', 64), + 0x1d: ('jeq', 'jdstsrcoff', '==', 64), 0x1f: ('sub', 'dstsrc', '-=', 64), 0x20: ('ldabsw', 'ldabs', None, 32), 0x24: ('mul32', 'dstimm', '*=', 32), @@ -156,7 +157,7 @@ class BPFDecoder(): 0x34: ('div32', 'dstimm', '/=', 32), 0x35: ('jge', 'jdstimmoff', '>=', 64), 0x37: ('div', 'dstimm', '/=', 64), - 0x38: ('ldabsdw', 'ldabs', None, 32), + 0x38: ('ldabsdw', 'ldabs', None, 64), 0x3c: ('div32', 'dstsrc', '/=', 32), 0x3d: ('jge', 'jdstsrcoff', '>=', 64), 0x3f: ('div', 'dstsrc', '/=', 64), @@ -229,13 +230,6 @@ class BPFDecoder(): 0xd5: ('jsle', 'jdstimmoff', 's<=', 64), 0xdc: ('endian32', 'dstsrc', 'endian', 32), 0xdd: ('jsle', 'jdstimmoff', 's<=', 64),} - - @classmethod - def decode_offset(cls, offset): - if offset >= 0: - return '+%d' % (offset) - else: - return '%d' % (offset) @classmethod def decode(cls, i, w, w1): @@ -248,25 +242,27 @@ def decode(cls, i, w, w1): return 'r%d %s 0x%x' % (w.dst, op, w.immu), 0 elif opclass == 'joff': - return 'goto %s <%d>' % (cls.decode_offset(w.offset), - i + w.offset), 0 + return 'goto %s <%d>' % ('%+d' % (w.offset), + i + w.offset + 1), 0 elif opclass == 'dstsrc': return 'r%d %s r%d' % (w.dst, op, w.src), 0 elif opclass == 'jdstimmoff': - return 'if r%d %s %d goto %s <%d>' % (w.dst, op, w.imm, - cls.decode_offset(w.offset), + return 'if r%d %s %d goto pc%s <%d>' % (w.dst, op, w.imm, + '%+d' % (w.offset), i + w.offset + 1), 0 elif opclass == 'jdstsrcoff': - return 'if r%d %s r%d goto %s <%d>' % (w.dst, op, w.src, - cls.decode_offset(w.offset), + return 'if r%d %s r%d goto pc%s <%d>' % (w.dst, op, w.src, + '%+d' % (w.offset), i + w.offset + 1), 0 elif opclass == 'lddw': # imm contains the file descriptor (FD) of the map being loaded; # the kernel will translate this into the proper address + if w1 is None: + raise Exception("lddw requires two instructions to be disassembled") if w1.imm == 0: return 'r%d = ' % (w.dst, w.imm), 1 imm = (w1.imm << 32) | w.imm @@ -277,19 +273,19 @@ def decode(cls, i, w, w1): elif opclass == 'ldind': return 'r0 = *(u%d*)skb[r%d %s]' % (bits, w.src, - cls.decode_offset(w.imm)), 0 + '%+d' % (w.imm)), 0 elif opclass == 'ldstsrcoff': return 'r%d = *(u%d*)(r%d %s)' % (w.dst, bits, w.src, - cls.decode_offset(w.offset)), 0 + '%+d' % (w.offset)), 0 elif opclass == 'sdstoffimm': return '*(u%d*)(r%d %s) = %d' % (bits, w.dst, - cls.decode_offset(w.offset), w.imm), 0 + '%+d' % (w.offset), w.imm), 0 elif opclass == 'sdstoffsrc': return '*(u%d*)(r%d %s) = r%d' % (bits, w.dst, - cls.decode_offset(w.offset), w.src), 0 + '%+d' % (w.offset), w.src), 0 elif opclass == 'dst': return 'r%d = %s (u%s)r%d' % (w.dst, op, bits, w.dst), 0 @@ -300,7 +296,7 @@ def decode(cls, i, w, w1): return '%s bpf_%s#%d' % (name, cls.bpf_helpers[w.immu], w.immu), 0 except IndexError: return '%s ' % (op, w.immu), 0 - return '%s %s' % (name, cls.decode_offset(w.imm)), 0 + return '%s %s' % (name, '%+d' % (w.imm)), 0 elif opclass == 'exit': return name, 0 else: @@ -309,26 +305,33 @@ def decode(cls, i, w, w1): except KeyError: return 'unknown <0x%x>' % (w.opcode) -def _print_instruction(i, w0, w1): +def disassemble_instruction(i, w0, w1=None): instr, skip = BPFDecoder.decode(i, w0, w1) - print("%4d: (%02x) %s" % (i, w0.opcode, instr)) - return skip - -def disassemble_prog(func_name, bpfstr): + return "%4d: (%02x) %s" % (i, w0.opcode, instr), skip + +def disassemble_str(bpfstr): ptr = ct.cast(ct.c_char_p(bpfstr), ct.POINTER(BPFInstr)) numinstr = len(bpfstr) / 8 w0 = ptr[0] skip = 0 - print("Disassemble of BPF program %s:" % (func_name)) + instr_list = [] for i in range(1, numinstr): w1 = ptr[i] if skip: skip -= 1 - print("%4d: (64-bit upper word)" % (i)) + instr_str = "%4d: (64-bit upper word)" % (i) else: - skip = _print_instruction(i - 1, w0, w1) + instr_str, skip = disassemble_instruction(i - 1, w0, w1) + instr_list.append(instr_str) w0 = w1 - _print_instruction(numinstr - 1, w0, None) + instr_str, skip = disassemble_instruction(numinstr - 1, w0, None) + instr_list.append(instr_str) + return instr_list + +def disassemble_prog(func_name, bpfstr): + instr_list = ["Disassemble of BPF program %s:" % (func_name)] + instr_list += disassemble_str(bpfstr) + return linesep.join(instr_list) class MapDecoder (): ctype2str = {ct.c_bool: u"_Bool", @@ -374,6 +377,7 @@ def format_size_info(cls, offset, size, enabled=False, bitoffset=None): @classmethod def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): + map_lines = [] try: for field_name, field_type in t._fields_: is_structured = (issubclass(field_type, ct.Structure) or @@ -384,12 +388,15 @@ def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): sizedesc = cls.format_size_info(offset + field_offset, field_size, sizeinfo) if is_structured: - print("%s%s%s {" % (indent, sizedesc, field_type_name)) - cls.print_ct_map(field_type, indent + " ", offset + field_offset) - print("%s} %s;" % (indent, field_name)) + map_lines.append("%s%s%s {" % (indent, sizedesc, field_type_name)) + map_lines += cls.print_ct_map(field_type, + indent + " ", + offset + field_offset) + map_lines.append("%s} %s;" % (indent, field_name)) else: - print("%s%s%s %s;" % (indent, sizedesc, - field_type_name, field_name)) + map_lines.append("%s%s%s %s;" % (indent, sizedesc, + field_type_name, + field_name)) except ValueError: # is a bit field offset_bits = 0 @@ -399,8 +406,10 @@ def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): field_type_name = cls.get_ct_name(field_type) sizedesc = cls.format_size_info(offset, offset_bits, sizeinfo, field_bits) - print("%s%s%s %s:%d;" % (indent, sizedesc, field_type_name, - field_name, field_bits)) + map_lines.append("%s%s%s %s:%d;" % (indent, sizedesc, + field_type_name, + field_name, + field_bits)) else: # end of previous bit field field_name, field_type = field @@ -411,10 +420,12 @@ def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False): offset_bits = 0 sizedesc = cls.format_size_info(offset + field_offset, field_size, sizeinfo) - print("%s%s%s %s;" % (indent, sizedesc, - field_type_name, field_name)) + map_lines.append("%s%s%s %s;" % (indent, sizedesc, + field_type_name, + field_name)) offset += field_offset offset_bits += field_bits + return map_lines @classmethod def print_map_ctype(cls, t, field_name, sizeinfo): @@ -422,20 +433,23 @@ def print_map_ctype(cls, t, field_name, sizeinfo): issubclass(t, ct.Union)) type_name = cls.get_ct_name(t); if is_structured: - print(" %s {" % (type_name)) - cls.print_ct_map(t, " ", sizeinfo=sizeinfo) - print(" } %s;" % (field_name)) + map_lines = [" %s {" % (type_name)] + map_lines += cls.print_ct_map(t, " ", sizeinfo=sizeinfo) + map_lines.append(" } %s;" % (field_name)) else: - print(" %s %s;" % (type_name, field_name)) - + map_lines = [" %s %s;" % (type_name, field_name)] + return map_lines @classmethod def decode_map(cls, map_name, map_obj, map_type, sizeinfo=False): - print('Layout of BPF type %s map %s (ID %d):' % (map_type, map_name, - map_obj.map_id)) - cls.print_map_ctype(map_obj.Key, 'key', sizeinfo=sizeinfo) - cls.print_map_ctype(map_obj.Leaf, 'value', sizeinfo=sizeinfo) + map_lines = ['Layout of BPF map %s (type %s, FD %d, ID %d):' % (map_name, + map_type, + map_obj.map_fd, + map_obj.map_id)] + map_lines += cls.print_map_ctype(map_obj.Key, 'key', sizeinfo=sizeinfo) + map_lines += cls.print_map_ctype(map_obj.Leaf, 'value', sizeinfo=sizeinfo) + return linesep.join(map_lines) def decode_map(map_name, map_obj, map_type, sizeinfo=False): map_type_name = get_table_type_name(map_type) - MapDecoder.decode_map(map_name, map_obj, map_type_name, sizeinfo=sizeinfo) + return MapDecoder.decode_map(map_name, map_obj, map_type_name, sizeinfo=sizeinfo) From 8c01fd451dbbe398943f66ca5e4f57eb868f7142 Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Tue, 19 Feb 2019 12:40:13 +0100 Subject: [PATCH 3/7] Disassembler test Signed-off-by: Oriol Arcas --- tests/python/CMakeLists.txt | 2 + tests/python/test_disassembler.py | 164 ++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100755 tests/python/test_disassembler.py diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index a16f76d32d42..7617c62f33fe 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -65,6 +65,8 @@ add_test(NAME py_test_percpu WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_percpu sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_percpu.py) add_test(NAME py_test_dump_func WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_dump_func simple ${CMAKE_CURRENT_SOURCE_DIR}/test_dump_func.py) +add_test(NAME py_test_disassembler WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${TEST_WRAPPER} py_disassembler simple ${CMAKE_CURRENT_SOURCE_DIR}/test_disassembler.py) add_test(NAME py_test_tools_smoke WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_tools_smoke sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_smoke.py) add_test(NAME py_test_tools_memleak WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/python/test_disassembler.py b/tests/python/test_disassembler.py new file mode 100755 index 000000000000..2da6a39effa9 --- /dev/null +++ b/tests/python/test_disassembler.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# Copyright (c) PLUMgrid, Inc. +# Licensed under the Apache License, Version 2.0 (the "License") + +# test program for the 'dump_func' method + +from bcc import BPF +from bcc import disassembler +import ctypes as ct +import random +from unittest import main, TestCase + +class BPFInstr(ct.Structure): + _pack_ = 1 + _fields_ = [('opcode', ct.c_uint8), + ('dst', ct.c_uint8, 4), + ('src', ct.c_uint8, 4), + ('offset', ct.c_int16), + ('imm', ct.c_int32)] + +class TestDisassembler(TestCase): + opcodes = {0x04: ("%dst += %imm"), + 0x05: ("goto %off <%jmp>"), + 0x07: ("%dst += %imm"), + 0x0c: ("%dst += %src"), + 0x0f: ("%dst += %src"), + 0x14: ("%dst -= %imm"), + 0x15: ("if %dst == %imm goto pc%off <%jmp>"), + 0x17: ("%dst -= %imm"), + #0x18: ("lddw"), + 0x1c: ("%dst -= %src"), + 0x1d: ("if %dst == %src goto pc%off <%jmp>"), + 0x1f: ("%dst -= %src"), + 0x20: ("r0 = *(u32*)skb[%imm]"), + 0x24: ("%dst *= %imm"), + 0x25: ("if %dst > %imm goto pc%off <%jmp>"), + 0x27: ("%dst *= %imm"), + 0x28: ("r0 = *(u16*)skb[%imm]"), + 0x2c: ("%dst *= %src"), + 0x2d: ("if %dst > %src goto pc%off <%jmp>"), + 0x2f: ("%dst *= %src"), + 0x30: ("r0 = *(u8*)skb[%imm]"), + 0x34: ("%dst /= %imm"), + 0x35: ("if %dst >= %imm goto pc%off <%jmp>"), + 0x37: ("%dst /= %imm"), + 0x38: ("r0 = *(u64*)skb[%imm]"), + 0x3c: ("%dst /= %src"), + 0x3d: ("if %dst >= %src goto pc%off <%jmp>"), + 0x3f: ("%dst /= %src"), + 0x40: ("r0 = *(u32*)skb[%src %sim]"), + 0x44: ("%dst |= %ibw"), + 0x45: ("if %dst & %imm goto pc%off <%jmp>"), + 0x47: ("%dst |= %ibw"), + 0x48: ("r0 = *(u16*)skb[%src %sim]"), + 0x4c: ("%dst |= %src"), + 0x4d: ("if %dst & %src goto pc%off <%jmp>"), + 0x4f: ("%dst |= %src"), + 0x50: ("r0 = *(u8*)skb[%src %sim]"), + 0x54: ("%dst &= %ibw"), + 0x55: ("if %dst != %imm goto pc%off <%jmp>"), + 0x57: ("%dst &= %ibw"), + 0x58: ("r0 = *(u64*)skb[%src %sim]"), + 0x5c: ("%dst &= %src"), + 0x5d: ("if %dst != %src goto pc%off <%jmp>"), + 0x5f: ("%dst &= %src"), + 0x61: ("%dst = *(u32*)(%src %off)"), + 0x62: ("*(u32*)(%dst %off) = %imm"), + 0x63: ("*(u32*)(%dst %off) = %src"), + 0x64: ("%dst <<= %imm"), + 0x65: ("if %dst s> %imm goto pc%off <%jmp>"), + 0x67: ("%dst <<= %imm"), + 0x69: ("%dst = *(u16*)(%src %off)"), + 0x6a: ("*(u16*)(%dst %off) = %imm"), + 0x6b: ("*(u16*)(%dst %off) = %src"), + 0x6c: ("%dst <<= %src"), + 0x6d: ("if %dst s> %src goto pc%off <%jmp>"), + 0x6f: ("%dst <<= %src"), + 0x71: ("%dst = *(u8*)(%src %off)"), + 0x72: ("*(u8*)(%dst %off) = %imm"), + 0x73: ("*(u8*)(%dst %off) = %src"), + 0x74: ("%dst >>= %imm"), + 0x75: ("if %dst s>= %imm goto pc%off <%jmp>"), + 0x77: ("%dst >>= %imm"), + 0x79: ("%dst = *(u64*)(%src %off)"), + 0x7a: ("*(u64*)(%dst %off) = %imm"), + 0x7b: ("*(u64*)(%dst %off) = %src"), + 0x7c: ("%dst >>= %src"), + 0x7d: ("if %dst s>= %src goto pc%off <%jmp>"), + 0x7f: ("%dst >>= %src"), + 0x84: ("%dst = ~ (u32)%dst"), + #0x85: ("call"), + 0x87: ("%dst = ~ (u64)%dst"), + 0x94: ("%dst %= %imm"), + 0x95: ("exit"), + 0x97: ("%dst %= %imm"), + 0x9c: ("%dst %= %src"), + 0x9f: ("%dst %= %src"), + 0xa4: ("%dst ^= %ibw"), + 0xa5: ("if %dst < %imm goto pc%off <%jmp>"), + 0xa7: ("%dst ^= %ibw"), + 0xac: ("%dst ^= %src"), + 0xad: ("if %dst < %src goto pc%off <%jmp>"), + 0xaf: ("%dst ^= %src"), + 0xb4: ("%dst = %imm"), + 0xb5: ("if %dst <= %imm goto pc%off <%jmp>"), + 0xb7: ("%dst = %imm"), + 0xbc: ("%dst = %src"), + 0xbd: ("if %dst <= %src goto pc%off <%jmp>"), + 0xbf: ("%dst = %src"), + 0xc4: ("%dst s>>= %imm"), + 0xc5: ("if %dst s< %imm goto pc%off <%jmp>"), + 0xc7: ("%dst s>>= %imm"), + 0xcc: ("%dst s>>= %src"), + 0xcd: ("if %dst s< %src goto pc%off <%jmp>"), + 0xcf: ("%dst s>>= %src"), + 0xd5: ("if %dst s<= %imm goto pc%off <%jmp>"), + 0xdc: ("%dst endian %src"), + 0xdd: ("if %dst s<= %imm goto pc%off <%jmp>"),} + + @classmethod + def build_instr(cls, op): + dst = random.randint(0, 0xf) + src = random.randint(0, 0xf) + offset = random.randint(0, 0xffff) + imm = random.randint(0, 0xffffffff) + return BPFInstr(op, dst, src, offset, imm) + + @classmethod + def format_instr(cls, instr, fmt): + uimm = ct.c_uint32(instr.imm).value + return (fmt.replace("%dst", "r%d" % (instr.dst)) + .replace("%src", "r%d" % (instr.src)) + .replace("%imm", "%d" % (instr.imm)) + .replace("%ibw", "0x%x" % (uimm)) + .replace("%sim", "%+d" % (instr.imm)) + .replace("%off", "%+d" % (instr.offset)) + .replace("%jmp", "%d" % (instr.offset + 1))) + + def test_func(self): + b = BPF(text=""" + int entry(void) + { + return 1; + }""") + + self.assertEqual( + """Disassemble of BPF program entry: + 0: (b7) r0 = 1 + 1: (95) exit""", + b.disassemble_func("entry")) + + def test_bpf_isa(self): + for op, instr_fmt in self.opcodes.iteritems(): + instr_fmt + if instr_fmt is None: + continue + instr = self.build_instr(op) + instr_str = ct.string_at(ct.addressof(instr), ct.sizeof(instr)) + target_text = self.format_instr(instr, instr_fmt) + self.assertEqual(disassembler.disassemble_str(instr_str)[0], + "%4d: (%02x) %s" % (0, op, target_text)) + +if __name__ == "__main__": + main() From 18bc00a30671692e6ce639c7b84b684b950d099a Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Tue, 19 Feb 2019 12:52:41 +0100 Subject: [PATCH 4/7] Map layout decoder test Signed-off-by: Oriol Arcas --- tests/python/test_disassembler.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/python/test_disassembler.py b/tests/python/test_disassembler.py index 2da6a39effa9..a03136c7cb6b 100755 --- a/tests/python/test_disassembler.py +++ b/tests/python/test_disassembler.py @@ -138,16 +138,31 @@ def format_instr(cls, instr, fmt): def test_func(self): b = BPF(text=""" - int entry(void) + struct key_t {int a; short b; struct {int c:4; int d:8;} e;} __attribute__((__packed__)); + BPF_HASH(test_map, struct key_t); + int test_func(void) { return 1; }""") self.assertEqual( - """Disassemble of BPF program entry: + """Disassemble of BPF program test_func: 0: (b7) r0 = 1 1: (95) exit""", - b.disassemble_func("entry")) + b.disassemble_func("test_func")) + + self.assertEqual( + """Layout of BPF map test_map (type HASH, FD 3, ID 0): + struct { + int a; + short b; + struct { + int c:4; + int d:8; + } e; + } key; + unsigned long long value;""", + b.decode_table("test_map")) def test_bpf_isa(self): for op, instr_fmt in self.opcodes.iteritems(): From f7504a7210a62207da76030df79b141987435783 Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Tue, 19 Feb 2019 12:57:05 +0100 Subject: [PATCH 5/7] Corrected description from disassembler test Signed-off-by: Oriol Arcas --- tests/python/test_disassembler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/test_disassembler.py b/tests/python/test_disassembler.py index a03136c7cb6b..b134e2aa2a09 100755 --- a/tests/python/test_disassembler.py +++ b/tests/python/test_disassembler.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -# Copyright (c) PLUMgrid, Inc. +# Copyright (c) Clevernet # Licensed under the Apache License, Version 2.0 (the "License") -# test program for the 'dump_func' method +# test program for the 'disassemble_func' and 'decode_table' methods from bcc import BPF from bcc import disassembler From caa2b2f155e736917da387f9cf5eeb0f4e850ea7 Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Wed, 20 Feb 2019 15:15:03 +0100 Subject: [PATCH 6/7] Use sudo to test disassembler Signed-off-by: Oriol Arcas --- tests/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 7617c62f33fe..5d4079b04313 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -66,7 +66,7 @@ add_test(NAME py_test_percpu WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} add_test(NAME py_test_dump_func WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_dump_func simple ${CMAKE_CURRENT_SOURCE_DIR}/test_dump_func.py) add_test(NAME py_test_disassembler WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${TEST_WRAPPER} py_disassembler simple ${CMAKE_CURRENT_SOURCE_DIR}/test_disassembler.py) + COMMAND ${TEST_WRAPPER} py_test_disassembler sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_disassembler.py) add_test(NAME py_test_tools_smoke WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_tools_smoke sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_smoke.py) add_test(NAME py_test_tools_memleak WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} From 91da7d26fe7e8fd400b303b9d5be208a4215610d Mon Sep 17 00:00:00 2001 From: Oriol Arcas Date: Fri, 22 Feb 2019 12:28:06 +0100 Subject: [PATCH 7/7] Fix Python 3 compatibility of disassembler test Signed-off-by: Oriol Arcas --- src/python/bcc/disassembler.py | 2 +- tests/python/test_disassembler.py | 196 +++++++++++++++--------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/python/bcc/disassembler.py b/src/python/bcc/disassembler.py index e691fb444d7d..6e1593d5202c 100644 --- a/src/python/bcc/disassembler.py +++ b/src/python/bcc/disassembler.py @@ -311,7 +311,7 @@ def disassemble_instruction(i, w0, w1=None): def disassemble_str(bpfstr): ptr = ct.cast(ct.c_char_p(bpfstr), ct.POINTER(BPFInstr)) - numinstr = len(bpfstr) / 8 + numinstr = int(len(bpfstr) / 8) w0 = ptr[0] skip = 0 instr_list = [] diff --git a/tests/python/test_disassembler.py b/tests/python/test_disassembler.py index b134e2aa2a09..89c4ec5606a1 100755 --- a/tests/python/test_disassembler.py +++ b/tests/python/test_disassembler.py @@ -19,103 +19,103 @@ class BPFInstr(ct.Structure): ('imm', ct.c_int32)] class TestDisassembler(TestCase): - opcodes = {0x04: ("%dst += %imm"), - 0x05: ("goto %off <%jmp>"), - 0x07: ("%dst += %imm"), - 0x0c: ("%dst += %src"), - 0x0f: ("%dst += %src"), - 0x14: ("%dst -= %imm"), - 0x15: ("if %dst == %imm goto pc%off <%jmp>"), - 0x17: ("%dst -= %imm"), - #0x18: ("lddw"), - 0x1c: ("%dst -= %src"), - 0x1d: ("if %dst == %src goto pc%off <%jmp>"), - 0x1f: ("%dst -= %src"), - 0x20: ("r0 = *(u32*)skb[%imm]"), - 0x24: ("%dst *= %imm"), - 0x25: ("if %dst > %imm goto pc%off <%jmp>"), - 0x27: ("%dst *= %imm"), - 0x28: ("r0 = *(u16*)skb[%imm]"), - 0x2c: ("%dst *= %src"), - 0x2d: ("if %dst > %src goto pc%off <%jmp>"), - 0x2f: ("%dst *= %src"), - 0x30: ("r0 = *(u8*)skb[%imm]"), - 0x34: ("%dst /= %imm"), - 0x35: ("if %dst >= %imm goto pc%off <%jmp>"), - 0x37: ("%dst /= %imm"), - 0x38: ("r0 = *(u64*)skb[%imm]"), - 0x3c: ("%dst /= %src"), - 0x3d: ("if %dst >= %src goto pc%off <%jmp>"), - 0x3f: ("%dst /= %src"), - 0x40: ("r0 = *(u32*)skb[%src %sim]"), - 0x44: ("%dst |= %ibw"), - 0x45: ("if %dst & %imm goto pc%off <%jmp>"), - 0x47: ("%dst |= %ibw"), - 0x48: ("r0 = *(u16*)skb[%src %sim]"), - 0x4c: ("%dst |= %src"), - 0x4d: ("if %dst & %src goto pc%off <%jmp>"), - 0x4f: ("%dst |= %src"), - 0x50: ("r0 = *(u8*)skb[%src %sim]"), - 0x54: ("%dst &= %ibw"), - 0x55: ("if %dst != %imm goto pc%off <%jmp>"), - 0x57: ("%dst &= %ibw"), - 0x58: ("r0 = *(u64*)skb[%src %sim]"), - 0x5c: ("%dst &= %src"), - 0x5d: ("if %dst != %src goto pc%off <%jmp>"), - 0x5f: ("%dst &= %src"), - 0x61: ("%dst = *(u32*)(%src %off)"), - 0x62: ("*(u32*)(%dst %off) = %imm"), - 0x63: ("*(u32*)(%dst %off) = %src"), - 0x64: ("%dst <<= %imm"), - 0x65: ("if %dst s> %imm goto pc%off <%jmp>"), - 0x67: ("%dst <<= %imm"), - 0x69: ("%dst = *(u16*)(%src %off)"), - 0x6a: ("*(u16*)(%dst %off) = %imm"), - 0x6b: ("*(u16*)(%dst %off) = %src"), - 0x6c: ("%dst <<= %src"), - 0x6d: ("if %dst s> %src goto pc%off <%jmp>"), - 0x6f: ("%dst <<= %src"), - 0x71: ("%dst = *(u8*)(%src %off)"), - 0x72: ("*(u8*)(%dst %off) = %imm"), - 0x73: ("*(u8*)(%dst %off) = %src"), - 0x74: ("%dst >>= %imm"), - 0x75: ("if %dst s>= %imm goto pc%off <%jmp>"), - 0x77: ("%dst >>= %imm"), - 0x79: ("%dst = *(u64*)(%src %off)"), - 0x7a: ("*(u64*)(%dst %off) = %imm"), - 0x7b: ("*(u64*)(%dst %off) = %src"), - 0x7c: ("%dst >>= %src"), - 0x7d: ("if %dst s>= %src goto pc%off <%jmp>"), - 0x7f: ("%dst >>= %src"), - 0x84: ("%dst = ~ (u32)%dst"), - #0x85: ("call"), - 0x87: ("%dst = ~ (u64)%dst"), - 0x94: ("%dst %= %imm"), - 0x95: ("exit"), - 0x97: ("%dst %= %imm"), - 0x9c: ("%dst %= %src"), - 0x9f: ("%dst %= %src"), - 0xa4: ("%dst ^= %ibw"), - 0xa5: ("if %dst < %imm goto pc%off <%jmp>"), - 0xa7: ("%dst ^= %ibw"), - 0xac: ("%dst ^= %src"), - 0xad: ("if %dst < %src goto pc%off <%jmp>"), - 0xaf: ("%dst ^= %src"), - 0xb4: ("%dst = %imm"), - 0xb5: ("if %dst <= %imm goto pc%off <%jmp>"), - 0xb7: ("%dst = %imm"), - 0xbc: ("%dst = %src"), - 0xbd: ("if %dst <= %src goto pc%off <%jmp>"), - 0xbf: ("%dst = %src"), - 0xc4: ("%dst s>>= %imm"), - 0xc5: ("if %dst s< %imm goto pc%off <%jmp>"), - 0xc7: ("%dst s>>= %imm"), - 0xcc: ("%dst s>>= %src"), - 0xcd: ("if %dst s< %src goto pc%off <%jmp>"), - 0xcf: ("%dst s>>= %src"), - 0xd5: ("if %dst s<= %imm goto pc%off <%jmp>"), - 0xdc: ("%dst endian %src"), - 0xdd: ("if %dst s<= %imm goto pc%off <%jmp>"),} + opcodes = [(0x04, "%dst += %imm"), + (0x05, "goto %off <%jmp>"), + (0x07, "%dst += %imm"), + (0x0c, "%dst += %src"), + (0x0f, "%dst += %src"), + (0x14, "%dst -= %imm"), + (0x15, "if %dst == %imm goto pc%off <%jmp>"), + (0x17, "%dst -= %imm"), + #(0x18, "lddw"), + (0x1c, "%dst -= %src"), + (0x1d, "if %dst == %src goto pc%off <%jmp>"), + (0x1f, "%dst -= %src"), + (0x20, "r0 = *(u32*)skb[%imm]"), + (0x24, "%dst *= %imm"), + (0x25, "if %dst > %imm goto pc%off <%jmp>"), + (0x27, "%dst *= %imm"), + (0x28, "r0 = *(u16*)skb[%imm]"), + (0x2c, "%dst *= %src"), + (0x2d, "if %dst > %src goto pc%off <%jmp>"), + (0x2f, "%dst *= %src"), + (0x30, "r0 = *(u8*)skb[%imm]"), + (0x34, "%dst /= %imm"), + (0x35, "if %dst >= %imm goto pc%off <%jmp>"), + (0x37, "%dst /= %imm"), + (0x38, "r0 = *(u64*)skb[%imm]"), + (0x3c, "%dst /= %src"), + (0x3d, "if %dst >= %src goto pc%off <%jmp>"), + (0x3f, "%dst /= %src"), + (0x40, "r0 = *(u32*)skb[%src %sim]"), + (0x44, "%dst |= %ibw"), + (0x45, "if %dst & %imm goto pc%off <%jmp>"), + (0x47, "%dst |= %ibw"), + (0x48, "r0 = *(u16*)skb[%src %sim]"), + (0x4c, "%dst |= %src"), + (0x4d, "if %dst & %src goto pc%off <%jmp>"), + (0x4f, "%dst |= %src"), + (0x50, "r0 = *(u8*)skb[%src %sim]"), + (0x54, "%dst &= %ibw"), + (0x55, "if %dst != %imm goto pc%off <%jmp>"), + (0x57, "%dst &= %ibw"), + (0x58, "r0 = *(u64*)skb[%src %sim]"), + (0x5c, "%dst &= %src"), + (0x5d, "if %dst != %src goto pc%off <%jmp>"), + (0x5f, "%dst &= %src"), + (0x61, "%dst = *(u32*)(%src %off)"), + (0x62, "*(u32*)(%dst %off) = %imm"), + (0x63, "*(u32*)(%dst %off) = %src"), + (0x64, "%dst <<= %imm"), + (0x65, "if %dst s> %imm goto pc%off <%jmp>"), + (0x67, "%dst <<= %imm"), + (0x69, "%dst = *(u16*)(%src %off)"), + (0x6a, "*(u16*)(%dst %off) = %imm"), + (0x6b, "*(u16*)(%dst %off) = %src"), + (0x6c, "%dst <<= %src"), + (0x6d, "if %dst s> %src goto pc%off <%jmp>"), + (0x6f, "%dst <<= %src"), + (0x71, "%dst = *(u8*)(%src %off)"), + (0x72, "*(u8*)(%dst %off) = %imm"), + (0x73, "*(u8*)(%dst %off) = %src"), + (0x74, "%dst >>= %imm"), + (0x75, "if %dst s>= %imm goto pc%off <%jmp>"), + (0x77, "%dst >>= %imm"), + (0x79, "%dst = *(u64*)(%src %off)"), + (0x7a, "*(u64*)(%dst %off) = %imm"), + (0x7b, "*(u64*)(%dst %off) = %src"), + (0x7c, "%dst >>= %src"), + (0x7d, "if %dst s>= %src goto pc%off <%jmp>"), + (0x7f, "%dst >>= %src"), + (0x84, "%dst = ~ (u32)%dst"), + #(0x85, "call"), + (0x87, "%dst = ~ (u64)%dst"), + (0x94, "%dst %= %imm"), + (0x95, "exit"), + (0x97, "%dst %= %imm"), + (0x9c, "%dst %= %src"), + (0x9f, "%dst %= %src"), + (0xa4, "%dst ^= %ibw"), + (0xa5, "if %dst < %imm goto pc%off <%jmp>"), + (0xa7, "%dst ^= %ibw"), + (0xac, "%dst ^= %src"), + (0xad, "if %dst < %src goto pc%off <%jmp>"), + (0xaf, "%dst ^= %src"), + (0xb4, "%dst = %imm"), + (0xb5, "if %dst <= %imm goto pc%off <%jmp>"), + (0xb7, "%dst = %imm"), + (0xbc, "%dst = %src"), + (0xbd, "if %dst <= %src goto pc%off <%jmp>"), + (0xbf, "%dst = %src"), + (0xc4, "%dst s>>= %imm"), + (0xc5, "if %dst s< %imm goto pc%off <%jmp>"), + (0xc7, "%dst s>>= %imm"), + (0xcc, "%dst s>>= %src"), + (0xcd, "if %dst s< %src goto pc%off <%jmp>"), + (0xcf, "%dst s>>= %src"), + (0xd5, "if %dst s<= %imm goto pc%off <%jmp>"), + (0xdc, "%dst endian %src"), + (0xdd, "if %dst s<= %imm goto pc%off <%jmp>"),] @classmethod def build_instr(cls, op): @@ -165,7 +165,7 @@ def test_func(self): b.decode_table("test_map")) def test_bpf_isa(self): - for op, instr_fmt in self.opcodes.iteritems(): + for op, instr_fmt in self.opcodes: instr_fmt if instr_fmt is None: continue