1#!/usr/bin/env python3 2# Copyright (c) Clevernet 3# Licensed under the Apache License, Version 2.0 (the "License") 4 5# test program for the 'disassemble_func' and 'decode_table' methods 6 7from bcc import BPF 8from bcc import disassembler 9import ctypes as ct 10import random 11from unittest import main, TestCase 12 13class BPFInstr(ct.Structure): 14 _pack_ = 1 15 _fields_ = [('opcode', ct.c_uint8), 16 ('dst', ct.c_uint8, 4), 17 ('src', ct.c_uint8, 4), 18 ('offset', ct.c_int16), 19 ('imm', ct.c_int32)] 20 21class TestDisassembler(TestCase): 22 opcodes = [(0x04, "%dst += %imm"), 23 (0x05, "goto %off <%jmp>"), 24 (0x07, "%dst += %imm"), 25 (0x0c, "%dst += %src"), 26 (0x0f, "%dst += %src"), 27 (0x14, "%dst -= %imm"), 28 (0x15, "if %dst == %imm goto pc%off <%jmp>"), 29 (0x17, "%dst -= %imm"), 30 #(0x18, "lddw"), 31 (0x1c, "%dst -= %src"), 32 (0x1d, "if %dst == %src goto pc%off <%jmp>"), 33 (0x1f, "%dst -= %src"), 34 (0x20, "r0 = *(u32*)skb[%imm]"), 35 (0x24, "%dst *= %imm"), 36 (0x25, "if %dst > %imm goto pc%off <%jmp>"), 37 (0x27, "%dst *= %imm"), 38 (0x28, "r0 = *(u16*)skb[%imm]"), 39 (0x2c, "%dst *= %src"), 40 (0x2d, "if %dst > %src goto pc%off <%jmp>"), 41 (0x2f, "%dst *= %src"), 42 (0x30, "r0 = *(u8*)skb[%imm]"), 43 (0x34, "%dst /= %imm"), 44 (0x35, "if %dst >= %imm goto pc%off <%jmp>"), 45 (0x37, "%dst /= %imm"), 46 (0x38, "r0 = *(u64*)skb[%imm]"), 47 (0x3c, "%dst /= %src"), 48 (0x3d, "if %dst >= %src goto pc%off <%jmp>"), 49 (0x3f, "%dst /= %src"), 50 (0x40, "r0 = *(u32*)skb[%src %sim]"), 51 (0x44, "%dst |= %ibw"), 52 (0x45, "if %dst & %imm goto pc%off <%jmp>"), 53 (0x47, "%dst |= %ibw"), 54 (0x48, "r0 = *(u16*)skb[%src %sim]"), 55 (0x4c, "%dst |= %src"), 56 (0x4d, "if %dst & %src goto pc%off <%jmp>"), 57 (0x4f, "%dst |= %src"), 58 (0x50, "r0 = *(u8*)skb[%src %sim]"), 59 (0x54, "%dst &= %ibw"), 60 (0x55, "if %dst != %imm goto pc%off <%jmp>"), 61 (0x57, "%dst &= %ibw"), 62 (0x58, "r0 = *(u64*)skb[%src %sim]"), 63 (0x5c, "%dst &= %src"), 64 (0x5d, "if %dst != %src goto pc%off <%jmp>"), 65 (0x5f, "%dst &= %src"), 66 (0x61, "%dst = *(u32*)(%src %off)"), 67 (0x62, "*(u32*)(%dst %off) = %imm"), 68 (0x63, "*(u32*)(%dst %off) = %src"), 69 (0x64, "%dst <<= %imm"), 70 (0x65, "if %dst s> %imm goto pc%off <%jmp>"), 71 (0x67, "%dst <<= %imm"), 72 (0x69, "%dst = *(u16*)(%src %off)"), 73 (0x6a, "*(u16*)(%dst %off) = %imm"), 74 (0x6b, "*(u16*)(%dst %off) = %src"), 75 (0x6c, "%dst <<= %src"), 76 (0x6d, "if %dst s> %src goto pc%off <%jmp>"), 77 (0x6f, "%dst <<= %src"), 78 (0x71, "%dst = *(u8*)(%src %off)"), 79 (0x72, "*(u8*)(%dst %off) = %imm"), 80 (0x73, "*(u8*)(%dst %off) = %src"), 81 (0x74, "%dst >>= %imm"), 82 (0x75, "if %dst s>= %imm goto pc%off <%jmp>"), 83 (0x77, "%dst >>= %imm"), 84 (0x79, "%dst = *(u64*)(%src %off)"), 85 (0x7a, "*(u64*)(%dst %off) = %imm"), 86 (0x7b, "*(u64*)(%dst %off) = %src"), 87 (0x7c, "%dst >>= %src"), 88 (0x7d, "if %dst s>= %src goto pc%off <%jmp>"), 89 (0x7f, "%dst >>= %src"), 90 (0x84, "%dst = ~ (u32)%dst"), 91 #(0x85, "call"), 92 (0x87, "%dst = ~ (u64)%dst"), 93 (0x94, "%dst %= %imm"), 94 (0x95, "exit"), 95 (0x97, "%dst %= %imm"), 96 (0x9c, "%dst %= %src"), 97 (0x9f, "%dst %= %src"), 98 (0xa4, "%dst ^= %ibw"), 99 (0xa5, "if %dst < %imm goto pc%off <%jmp>"), 100 (0xa7, "%dst ^= %ibw"), 101 (0xac, "%dst ^= %src"), 102 (0xad, "if %dst < %src goto pc%off <%jmp>"), 103 (0xaf, "%dst ^= %src"), 104 (0xb4, "%dst = %imm"), 105 (0xb5, "if %dst <= %imm goto pc%off <%jmp>"), 106 (0xb7, "%dst = %imm"), 107 (0xbc, "%dst = %src"), 108 (0xbd, "if %dst <= %src goto pc%off <%jmp>"), 109 (0xbf, "%dst = %src"), 110 (0xc4, "%dst s>>= %imm"), 111 (0xc5, "if %dst s< %imm goto pc%off <%jmp>"), 112 (0xc7, "%dst s>>= %imm"), 113 (0xcc, "%dst s>>= %src"), 114 (0xcd, "if %dst s< %src goto pc%off <%jmp>"), 115 (0xcf, "%dst s>>= %src"), 116 (0xd5, "if %dst s<= %imm goto pc%off <%jmp>"), 117 (0xdc, "%dst endian %src"), 118 (0xdd, "if %dst s<= %imm goto pc%off <%jmp>"),] 119 120 @classmethod 121 def build_instr(cls, op): 122 dst = random.randint(0, 0xf) 123 src = random.randint(0, 0xf) 124 offset = random.randint(0, 0xffff) 125 imm = random.randint(0, 0xffffffff) 126 return BPFInstr(op, dst, src, offset, imm) 127 128 @classmethod 129 def format_instr(cls, instr, fmt): 130 uimm = ct.c_uint32(instr.imm).value 131 return (fmt.replace("%dst", "r%d" % (instr.dst)) 132 .replace("%src", "r%d" % (instr.src)) 133 .replace("%imm", "%d" % (instr.imm)) 134 .replace("%ibw", "0x%x" % (uimm)) 135 .replace("%sim", "%+d" % (instr.imm)) 136 .replace("%off", "%+d" % (instr.offset)) 137 .replace("%jmp", "%d" % (instr.offset + 1))) 138 139 def test_func(self): 140 b = BPF(text=b""" 141 struct key_t {int a; short b; struct {int c:4; int d:8;} e;} __attribute__((__packed__)); 142 BPF_HASH(test_map, struct key_t); 143 int test_func(void) 144 { 145 return 1; 146 }""") 147 148 self.assertEqual( 149 """Disassemble of BPF program b'test_func': 150 0: (b7) r0 = 1 151 1: (95) exit""", 152 b.disassemble_func(b"test_func")) 153 154 def _assert_equal_ignore_fd_id(s1, s2): 155 # In first line of string like 156 # Layout of BPF map test_map (type HASH, FD 3, ID 0): 157 # Ignore everything from FD to end-of-line 158 # Compare rest of string normally 159 s1_lines = s1.split('\n') 160 s2_lines = s2.split('\n') 161 s1_first_cut = s1_lines[0] 162 s1_first_cut = s1_first_cut[0:s1_first_cut.index("FD")] 163 s2_first_cut = s2_lines[0] 164 s2_first_cut = s2_first_cut[0:s2_first_cut.index("FD")] 165 166 self.assertEqual(s1_first_cut, s2_first_cut) 167 168 s1_rest = '\n'.join(s1_lines[1:]) 169 s2_rest = '\n'.join(s2_lines[1:]) 170 self.assertEqual(s1_rest, s2_rest) 171 172 _assert_equal_ignore_fd_id( 173 """Layout of BPF map b'test_map' (type HASH, FD 3, ID 0): 174 struct { 175 int a; 176 short b; 177 struct { 178 int c:4; 179 int d:8; 180 } e; 181 } key; 182 unsigned long long value;""", 183 b.decode_table(b"test_map")) 184 185 def test_bpf_isa(self): 186 for op, instr_fmt in self.opcodes: 187 instr_fmt 188 if instr_fmt is None: 189 continue 190 instr = self.build_instr(op) 191 instr_str = ct.string_at(ct.addressof(instr), ct.sizeof(instr)) 192 target_text = self.format_instr(instr, instr_fmt) 193 self.assertEqual(disassembler.disassemble_str(instr_str)[0], 194 "%4d: (%02x) %s" % (0, op, target_text)) 195 196if __name__ == "__main__": 197 main() 198