1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com |
3 | * Copyright (c) 2016 Facebook |
4 | */ |
5 | |
6 | #include <linux/bpf.h> |
7 | |
8 | #include "disasm.h" |
9 | |
10 | #define __BPF_FUNC_STR_FN(x) [BPF_FUNC_ ## x] = __stringify(bpf_ ## x) |
11 | static const char * const func_id_str[] = { |
12 | __BPF_FUNC_MAPPER(__BPF_FUNC_STR_FN) |
13 | }; |
14 | #undef __BPF_FUNC_STR_FN |
15 | |
16 | static const char *__func_get_name(const struct bpf_insn_cbs *cbs, |
17 | const struct bpf_insn *insn, |
18 | char *buff, size_t len) |
19 | { |
20 | BUILD_BUG_ON(ARRAY_SIZE(func_id_str) != __BPF_FUNC_MAX_ID); |
21 | |
22 | if (!insn->src_reg && |
23 | insn->imm >= 0 && insn->imm < __BPF_FUNC_MAX_ID && |
24 | func_id_str[insn->imm]) |
25 | return func_id_str[insn->imm]; |
26 | |
27 | if (cbs && cbs->cb_call) { |
28 | const char *res; |
29 | |
30 | res = cbs->cb_call(cbs->private_data, insn); |
31 | if (res) |
32 | return res; |
33 | } |
34 | |
35 | if (insn->src_reg == BPF_PSEUDO_CALL) |
36 | snprintf(buf: buff, size: len, fmt: "%+d" , insn->imm); |
37 | else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) |
38 | snprintf(buf: buff, size: len, fmt: "kernel-function" ); |
39 | |
40 | return buff; |
41 | } |
42 | |
43 | static const char *__func_imm_name(const struct bpf_insn_cbs *cbs, |
44 | const struct bpf_insn *insn, |
45 | u64 full_imm, char *buff, size_t len) |
46 | { |
47 | if (cbs && cbs->cb_imm) |
48 | return cbs->cb_imm(cbs->private_data, insn, full_imm); |
49 | |
50 | snprintf(buf: buff, size: len, fmt: "0x%llx" , (unsigned long long)full_imm); |
51 | return buff; |
52 | } |
53 | |
54 | const char *func_id_name(int id) |
55 | { |
56 | if (id >= 0 && id < __BPF_FUNC_MAX_ID && func_id_str[id]) |
57 | return func_id_str[id]; |
58 | else |
59 | return "unknown" ; |
60 | } |
61 | |
62 | const char *const bpf_class_string[8] = { |
63 | [BPF_LD] = "ld" , |
64 | [BPF_LDX] = "ldx" , |
65 | [BPF_ST] = "st" , |
66 | [BPF_STX] = "stx" , |
67 | [BPF_ALU] = "alu" , |
68 | [BPF_JMP] = "jmp" , |
69 | [BPF_JMP32] = "jmp32" , |
70 | [BPF_ALU64] = "alu64" , |
71 | }; |
72 | |
73 | const char *const bpf_alu_string[16] = { |
74 | [BPF_ADD >> 4] = "+=" , |
75 | [BPF_SUB >> 4] = "-=" , |
76 | [BPF_MUL >> 4] = "*=" , |
77 | [BPF_DIV >> 4] = "/=" , |
78 | [BPF_OR >> 4] = "|=" , |
79 | [BPF_AND >> 4] = "&=" , |
80 | [BPF_LSH >> 4] = "<<=" , |
81 | [BPF_RSH >> 4] = ">>=" , |
82 | [BPF_NEG >> 4] = "neg" , |
83 | [BPF_MOD >> 4] = "%=" , |
84 | [BPF_XOR >> 4] = "^=" , |
85 | [BPF_MOV >> 4] = "=" , |
86 | [BPF_ARSH >> 4] = "s>>=" , |
87 | [BPF_END >> 4] = "endian" , |
88 | }; |
89 | |
90 | static const char *const bpf_alu_sign_string[16] = { |
91 | [BPF_DIV >> 4] = "s/=" , |
92 | [BPF_MOD >> 4] = "s%=" , |
93 | }; |
94 | |
95 | static const char *const bpf_movsx_string[4] = { |
96 | [0] = "(s8)" , |
97 | [1] = "(s16)" , |
98 | [3] = "(s32)" , |
99 | }; |
100 | |
101 | static const char *const bpf_atomic_alu_string[16] = { |
102 | [BPF_ADD >> 4] = "add" , |
103 | [BPF_AND >> 4] = "and" , |
104 | [BPF_OR >> 4] = "or" , |
105 | [BPF_XOR >> 4] = "xor" , |
106 | }; |
107 | |
108 | static const char *const bpf_ldst_string[] = { |
109 | [BPF_W >> 3] = "u32" , |
110 | [BPF_H >> 3] = "u16" , |
111 | [BPF_B >> 3] = "u8" , |
112 | [BPF_DW >> 3] = "u64" , |
113 | }; |
114 | |
115 | static const char *const bpf_ldsx_string[] = { |
116 | [BPF_W >> 3] = "s32" , |
117 | [BPF_H >> 3] = "s16" , |
118 | [BPF_B >> 3] = "s8" , |
119 | }; |
120 | |
121 | static const char *const bpf_jmp_string[16] = { |
122 | [BPF_JA >> 4] = "jmp" , |
123 | [BPF_JEQ >> 4] = "==" , |
124 | [BPF_JGT >> 4] = ">" , |
125 | [BPF_JLT >> 4] = "<" , |
126 | [BPF_JGE >> 4] = ">=" , |
127 | [BPF_JLE >> 4] = "<=" , |
128 | [BPF_JSET >> 4] = "&" , |
129 | [BPF_JNE >> 4] = "!=" , |
130 | [BPF_JSGT >> 4] = "s>" , |
131 | [BPF_JSLT >> 4] = "s<" , |
132 | [BPF_JSGE >> 4] = "s>=" , |
133 | [BPF_JSLE >> 4] = "s<=" , |
134 | [BPF_CALL >> 4] = "call" , |
135 | [BPF_EXIT >> 4] = "exit" , |
136 | }; |
137 | |
138 | static void print_bpf_end_insn(bpf_insn_print_t verbose, |
139 | void *private_data, |
140 | const struct bpf_insn *insn) |
141 | { |
142 | verbose(private_data, "(%02x) r%d = %s%d r%d\n" , |
143 | insn->code, insn->dst_reg, |
144 | BPF_SRC(insn->code) == BPF_TO_BE ? "be" : "le" , |
145 | insn->imm, insn->dst_reg); |
146 | } |
147 | |
148 | static void print_bpf_bswap_insn(bpf_insn_print_t verbose, |
149 | void *private_data, |
150 | const struct bpf_insn *insn) |
151 | { |
152 | verbose(private_data, "(%02x) r%d = bswap%d r%d\n" , |
153 | insn->code, insn->dst_reg, |
154 | insn->imm, insn->dst_reg); |
155 | } |
156 | |
157 | static bool is_sdiv_smod(const struct bpf_insn *insn) |
158 | { |
159 | return (BPF_OP(insn->code) == BPF_DIV || BPF_OP(insn->code) == BPF_MOD) && |
160 | insn->off == 1; |
161 | } |
162 | |
163 | static bool is_movsx(const struct bpf_insn *insn) |
164 | { |
165 | return BPF_OP(insn->code) == BPF_MOV && |
166 | (insn->off == 8 || insn->off == 16 || insn->off == 32); |
167 | } |
168 | |
169 | static bool is_addr_space_cast(const struct bpf_insn *insn) |
170 | { |
171 | return insn->code == (BPF_ALU64 | BPF_MOV | BPF_X) && |
172 | insn->off == BPF_ADDR_SPACE_CAST; |
173 | } |
174 | |
175 | void print_bpf_insn(const struct bpf_insn_cbs *cbs, |
176 | const struct bpf_insn *insn, |
177 | bool allow_ptr_leaks) |
178 | { |
179 | const bpf_insn_print_t verbose = cbs->cb_print; |
180 | u8 class = BPF_CLASS(insn->code); |
181 | |
182 | if (class == BPF_ALU || class == BPF_ALU64) { |
183 | if (BPF_OP(insn->code) == BPF_END) { |
184 | if (class == BPF_ALU64) |
185 | print_bpf_bswap_insn(verbose, private_data: cbs->private_data, insn); |
186 | else |
187 | print_bpf_end_insn(verbose, private_data: cbs->private_data, insn); |
188 | } else if (BPF_OP(insn->code) == BPF_NEG) { |
189 | verbose(cbs->private_data, "(%02x) %c%d = -%c%d\n" , |
190 | insn->code, class == BPF_ALU ? 'w' : 'r', |
191 | insn->dst_reg, class == BPF_ALU ? 'w' : 'r', |
192 | insn->dst_reg); |
193 | } else if (is_addr_space_cast(insn)) { |
194 | verbose(cbs->private_data, "(%02x) r%d = addr_space_cast(r%d, %d, %d)\n" , |
195 | insn->code, insn->dst_reg, |
196 | insn->src_reg, ((u32)insn->imm) >> 16, (u16)insn->imm); |
197 | } else if (BPF_SRC(insn->code) == BPF_X) { |
198 | verbose(cbs->private_data, "(%02x) %c%d %s %s%c%d\n" , |
199 | insn->code, class == BPF_ALU ? 'w' : 'r', |
200 | insn->dst_reg, |
201 | is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4] |
202 | : bpf_alu_string[BPF_OP(insn->code) >> 4], |
203 | is_movsx(insn) ? bpf_movsx_string[(insn->off >> 3) - 1] : "" , |
204 | class == BPF_ALU ? 'w' : 'r', |
205 | insn->src_reg); |
206 | } else { |
207 | verbose(cbs->private_data, "(%02x) %c%d %s %d\n" , |
208 | insn->code, class == BPF_ALU ? 'w' : 'r', |
209 | insn->dst_reg, |
210 | is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4] |
211 | : bpf_alu_string[BPF_OP(insn->code) >> 4], |
212 | insn->imm); |
213 | } |
214 | } else if (class == BPF_STX) { |
215 | if (BPF_MODE(insn->code) == BPF_MEM) |
216 | verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = r%d\n" , |
217 | insn->code, |
218 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
219 | insn->dst_reg, |
220 | insn->off, insn->src_reg); |
221 | else if (BPF_MODE(insn->code) == BPF_ATOMIC && |
222 | (insn->imm == BPF_ADD || insn->imm == BPF_AND || |
223 | insn->imm == BPF_OR || insn->imm == BPF_XOR)) { |
224 | verbose(cbs->private_data, "(%02x) lock *(%s *)(r%d %+d) %s r%d\n" , |
225 | insn->code, |
226 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
227 | insn->dst_reg, insn->off, |
228 | bpf_alu_string[BPF_OP(insn->imm) >> 4], |
229 | insn->src_reg); |
230 | } else if (BPF_MODE(insn->code) == BPF_ATOMIC && |
231 | (insn->imm == (BPF_ADD | BPF_FETCH) || |
232 | insn->imm == (BPF_AND | BPF_FETCH) || |
233 | insn->imm == (BPF_OR | BPF_FETCH) || |
234 | insn->imm == (BPF_XOR | BPF_FETCH))) { |
235 | verbose(cbs->private_data, "(%02x) r%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)\n" , |
236 | insn->code, insn->src_reg, |
237 | BPF_SIZE(insn->code) == BPF_DW ? "64" : "" , |
238 | bpf_atomic_alu_string[BPF_OP(insn->imm) >> 4], |
239 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
240 | insn->dst_reg, insn->off, insn->src_reg); |
241 | } else if (BPF_MODE(insn->code) == BPF_ATOMIC && |
242 | insn->imm == BPF_CMPXCHG) { |
243 | verbose(cbs->private_data, "(%02x) r0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)\n" , |
244 | insn->code, |
245 | BPF_SIZE(insn->code) == BPF_DW ? "64" : "" , |
246 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
247 | insn->dst_reg, insn->off, |
248 | insn->src_reg); |
249 | } else if (BPF_MODE(insn->code) == BPF_ATOMIC && |
250 | insn->imm == BPF_XCHG) { |
251 | verbose(cbs->private_data, "(%02x) r%d = atomic%s_xchg((%s *)(r%d %+d), r%d)\n" , |
252 | insn->code, insn->src_reg, |
253 | BPF_SIZE(insn->code) == BPF_DW ? "64" : "" , |
254 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
255 | insn->dst_reg, insn->off, insn->src_reg); |
256 | } else { |
257 | verbose(cbs->private_data, "BUG_%02x\n" , insn->code); |
258 | } |
259 | } else if (class == BPF_ST) { |
260 | if (BPF_MODE(insn->code) == BPF_MEM) { |
261 | verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = %d\n" , |
262 | insn->code, |
263 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
264 | insn->dst_reg, |
265 | insn->off, insn->imm); |
266 | } else if (BPF_MODE(insn->code) == 0xc0 /* BPF_NOSPEC, no UAPI */) { |
267 | verbose(cbs->private_data, "(%02x) nospec\n" , insn->code); |
268 | } else { |
269 | verbose(cbs->private_data, "BUG_st_%02x\n" , insn->code); |
270 | } |
271 | } else if (class == BPF_LDX) { |
272 | if (BPF_MODE(insn->code) != BPF_MEM && BPF_MODE(insn->code) != BPF_MEMSX) { |
273 | verbose(cbs->private_data, "BUG_ldx_%02x\n" , insn->code); |
274 | return; |
275 | } |
276 | verbose(cbs->private_data, "(%02x) r%d = *(%s *)(r%d %+d)\n" , |
277 | insn->code, insn->dst_reg, |
278 | BPF_MODE(insn->code) == BPF_MEM ? |
279 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3] : |
280 | bpf_ldsx_string[BPF_SIZE(insn->code) >> 3], |
281 | insn->src_reg, insn->off); |
282 | } else if (class == BPF_LD) { |
283 | if (BPF_MODE(insn->code) == BPF_ABS) { |
284 | verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[%d]\n" , |
285 | insn->code, |
286 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
287 | insn->imm); |
288 | } else if (BPF_MODE(insn->code) == BPF_IND) { |
289 | verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[r%d + %d]\n" , |
290 | insn->code, |
291 | bpf_ldst_string[BPF_SIZE(insn->code) >> 3], |
292 | insn->src_reg, insn->imm); |
293 | } else if (BPF_MODE(insn->code) == BPF_IMM && |
294 | BPF_SIZE(insn->code) == BPF_DW) { |
295 | /* At this point, we already made sure that the second |
296 | * part of the ldimm64 insn is accessible. |
297 | */ |
298 | u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm; |
299 | bool is_ptr = insn->src_reg == BPF_PSEUDO_MAP_FD || |
300 | insn->src_reg == BPF_PSEUDO_MAP_VALUE; |
301 | char tmp[64]; |
302 | |
303 | if (is_ptr && !allow_ptr_leaks) |
304 | imm = 0; |
305 | |
306 | verbose(cbs->private_data, "(%02x) r%d = %s\n" , |
307 | insn->code, insn->dst_reg, |
308 | __func_imm_name(cbs, insn, full_imm: imm, |
309 | buff: tmp, len: sizeof(tmp))); |
310 | } else { |
311 | verbose(cbs->private_data, "BUG_ld_%02x\n" , insn->code); |
312 | return; |
313 | } |
314 | } else if (class == BPF_JMP32 || class == BPF_JMP) { |
315 | u8 opcode = BPF_OP(insn->code); |
316 | |
317 | if (opcode == BPF_CALL) { |
318 | char tmp[64]; |
319 | |
320 | if (insn->src_reg == BPF_PSEUDO_CALL) { |
321 | verbose(cbs->private_data, "(%02x) call pc%s\n" , |
322 | insn->code, |
323 | __func_get_name(cbs, insn, |
324 | buff: tmp, len: sizeof(tmp))); |
325 | } else { |
326 | strcpy(p: tmp, q: "unknown" ); |
327 | verbose(cbs->private_data, "(%02x) call %s#%d\n" , insn->code, |
328 | __func_get_name(cbs, insn, |
329 | buff: tmp, len: sizeof(tmp)), |
330 | insn->imm); |
331 | } |
332 | } else if (insn->code == (BPF_JMP | BPF_JA)) { |
333 | verbose(cbs->private_data, "(%02x) goto pc%+d\n" , |
334 | insn->code, insn->off); |
335 | } else if (insn->code == (BPF_JMP | BPF_JCOND) && |
336 | insn->src_reg == BPF_MAY_GOTO) { |
337 | verbose(cbs->private_data, "(%02x) may_goto pc%+d\n" , |
338 | insn->code, insn->off); |
339 | } else if (insn->code == (BPF_JMP32 | BPF_JA)) { |
340 | verbose(cbs->private_data, "(%02x) gotol pc%+d\n" , |
341 | insn->code, insn->imm); |
342 | } else if (insn->code == (BPF_JMP | BPF_EXIT)) { |
343 | verbose(cbs->private_data, "(%02x) exit\n" , insn->code); |
344 | } else if (BPF_SRC(insn->code) == BPF_X) { |
345 | verbose(cbs->private_data, |
346 | "(%02x) if %c%d %s %c%d goto pc%+d\n" , |
347 | insn->code, class == BPF_JMP32 ? 'w' : 'r', |
348 | insn->dst_reg, |
349 | bpf_jmp_string[BPF_OP(insn->code) >> 4], |
350 | class == BPF_JMP32 ? 'w' : 'r', |
351 | insn->src_reg, insn->off); |
352 | } else { |
353 | verbose(cbs->private_data, |
354 | "(%02x) if %c%d %s 0x%x goto pc%+d\n" , |
355 | insn->code, class == BPF_JMP32 ? 'w' : 'r', |
356 | insn->dst_reg, |
357 | bpf_jmp_string[BPF_OP(insn->code) >> 4], |
358 | insn->imm, insn->off); |
359 | } |
360 | } else { |
361 | verbose(cbs->private_data, "(%02x) %s\n" , |
362 | insn->code, bpf_class_string[class]); |
363 | } |
364 | } |
365 | |