/src/capstonenext/arch/BPF/BPFInstPrinter.c
Line | Count | Source |
1 | | /* Capstone Disassembly Engine */ |
2 | | /* BPF Backend by david942j <david942j@gmail.com>, 2019 */ |
3 | | /* SPDX-FileCopyrightText: 2024 Roee Toledano <roeetoledano10@gmail.com> */ |
4 | | /* SPDX-License-Identifier: BSD-3 */ |
5 | | |
6 | | #include <capstone/platform.h> |
7 | | |
8 | | #include "BPFConstants.h" |
9 | | #include "BPFInstPrinter.h" |
10 | | #include "BPFMapping.h" |
11 | | #include "../../Mapping.h" |
12 | | |
13 | | static cs_bpf_op *expand_bpf_operands(cs_bpf *bpf) |
14 | 43.7k | { |
15 | 43.7k | assert(bpf->op_count < 3); |
16 | 43.7k | return &bpf->operands[bpf->op_count++]; |
17 | 43.7k | } |
18 | | |
19 | | static void push_op_reg(cs_bpf *bpf, bpf_op_type val, uint8_t ac_mode) |
20 | 15.2k | { |
21 | 15.2k | cs_bpf_op *op = expand_bpf_operands(bpf); |
22 | | |
23 | 15.2k | op->type = BPF_OP_REG; |
24 | 15.2k | op->reg = val; |
25 | 15.2k | op->access = ac_mode; |
26 | 15.2k | } |
27 | | |
28 | | static void push_op_imm(cs_bpf *bpf, uint64_t val, const bool is_signed) |
29 | 12.8k | { |
30 | 12.8k | cs_bpf_op *op = expand_bpf_operands(bpf); |
31 | | |
32 | 12.8k | op->type = BPF_OP_IMM; |
33 | 12.8k | op->imm = val; |
34 | 12.8k | op->is_signed = is_signed; |
35 | 12.8k | } |
36 | | |
37 | | static void push_op_off(cs_bpf *bpf, uint32_t val, const bool is_signed) |
38 | 7.18k | { |
39 | 7.18k | cs_bpf_op *op = expand_bpf_operands(bpf); |
40 | | |
41 | 7.18k | op->type = BPF_OP_OFF; |
42 | 7.18k | op->off = val; |
43 | 7.18k | op->is_signed = is_signed; |
44 | 7.18k | } |
45 | | |
46 | | static void push_op_mem(cs_bpf *bpf, bpf_reg reg, uint32_t val, |
47 | | const bool is_signed, const bool is_pkt) |
48 | 7.56k | { |
49 | 7.56k | cs_bpf_op *op = expand_bpf_operands(bpf); |
50 | | |
51 | 7.56k | op->type = BPF_OP_MEM; |
52 | 7.56k | op->mem.base = reg; |
53 | 7.56k | op->mem.disp = val; |
54 | 7.56k | op->is_signed = is_signed; |
55 | 7.56k | op->is_pkt = is_pkt; |
56 | 7.56k | } |
57 | | |
58 | | static void push_op_mmem(cs_bpf *bpf, uint32_t val) |
59 | 361 | { |
60 | 361 | cs_bpf_op *op = expand_bpf_operands(bpf); |
61 | | |
62 | 361 | op->type = BPF_OP_MMEM; |
63 | 361 | op->mmem = val; |
64 | 361 | } |
65 | | |
66 | | static void push_op_msh(cs_bpf *bpf, uint32_t val) |
67 | 213 | { |
68 | 213 | cs_bpf_op *op = expand_bpf_operands(bpf); |
69 | | |
70 | 213 | op->type = BPF_OP_MSH; |
71 | 213 | op->msh = val; |
72 | 213 | } |
73 | | |
74 | | static void push_op_ext(cs_bpf *bpf, bpf_ext_type val) |
75 | 237 | { |
76 | 237 | cs_bpf_op *op = expand_bpf_operands(bpf); |
77 | | |
78 | 237 | op->type = BPF_OP_EXT; |
79 | 237 | op->ext = val; |
80 | 237 | } |
81 | | |
82 | | static void convert_operands(MCInst *MI, cs_bpf *bpf) |
83 | 24.9k | { |
84 | 24.9k | unsigned opcode = MCInst_getOpcode(MI); |
85 | 24.9k | unsigned mc_op_count = MCInst_getNumOperands(MI); |
86 | 24.9k | MCOperand *op; |
87 | 24.9k | MCOperand *op2; |
88 | | |
89 | 24.9k | bpf->op_count = 0; |
90 | 24.9k | if (BPF_CLASS(opcode) == BPF_CLASS_LD || |
91 | 19.8k | BPF_CLASS(opcode) == BPF_CLASS_LDX) { |
92 | 6.72k | switch (BPF_MODE(opcode)) { |
93 | 1.04k | case BPF_MODE_IMM: |
94 | 1.04k | if (EBPF_MODE(MI->csh->mode)) { |
95 | 83 | push_op_reg(bpf, |
96 | 83 | MCOperand_getReg( |
97 | 83 | MCInst_getOperand(MI, 0)), |
98 | 83 | CS_AC_WRITE); |
99 | 83 | push_op_imm(bpf, |
100 | 83 | MCOperand_getImm( |
101 | 83 | MCInst_getOperand(MI, 1)), |
102 | 83 | false); |
103 | 957 | } else { |
104 | 957 | push_op_imm(bpf, |
105 | 957 | MCOperand_getImm( |
106 | 957 | MCInst_getOperand(MI, 0)), |
107 | 957 | false); |
108 | 957 | } |
109 | 1.04k | break; |
110 | 2.03k | case BPF_MODE_ABS: |
111 | 2.03k | op = MCInst_getOperand(MI, 0); |
112 | 2.03k | push_op_mem(bpf, BPF_REG_INVALID, |
113 | 2.03k | (uint32_t)MCOperand_getImm(op), |
114 | 2.03k | EBPF_MODE(MI->csh->mode), true); |
115 | 2.03k | break; |
116 | 1.96k | case BPF_MODE_IND: |
117 | 1.96k | op = MCInst_getOperand(MI, 0); |
118 | 1.96k | if (EBPF_MODE(MI->csh->mode)) |
119 | 1.29k | push_op_mem(bpf, MCOperand_getReg(op), 0x0, |
120 | 1.29k | true, true); |
121 | 668 | else { |
122 | 668 | op2 = MCInst_getOperand(MI, 1); |
123 | 668 | push_op_mem(bpf, MCOperand_getReg(op), |
124 | 668 | (uint32_t)MCOperand_getImm(op2), |
125 | 668 | false, true); |
126 | 668 | } |
127 | 1.96k | break; |
128 | 1.23k | case BPF_MODE_MEM: |
129 | 1.23k | if (EBPF_MODE(MI->csh->mode)) { |
130 | | /* ldx{w,h,b,dw} dst, [src+off] */ |
131 | 1.02k | push_op_reg(bpf, |
132 | 1.02k | MCOperand_getReg( |
133 | 1.02k | MCInst_getOperand(MI, 0)), |
134 | 1.02k | CS_AC_WRITE); |
135 | 1.02k | op = MCInst_getOperand(MI, 1); |
136 | 1.02k | op2 = MCInst_getOperand(MI, 2); |
137 | 1.02k | push_op_mem(bpf, MCOperand_getReg(op), |
138 | 1.02k | (uint32_t)MCOperand_getImm(op2), |
139 | 1.02k | true, false); |
140 | 1.02k | } else { |
141 | 214 | push_op_mmem(bpf, |
142 | 214 | (uint32_t)MCOperand_getImm( |
143 | 214 | MCInst_getOperand(MI, 0))); |
144 | 214 | } |
145 | 1.23k | break; |
146 | 237 | case BPF_MODE_LEN: |
147 | 237 | push_op_ext(bpf, BPF_EXT_LEN); |
148 | 237 | break; |
149 | 213 | case BPF_MODE_MSH: |
150 | 213 | op = MCInst_getOperand(MI, 0); |
151 | 213 | push_op_msh(bpf, (uint32_t)MCOperand_getImm(op)); |
152 | 213 | break; |
153 | | /* case BPF_MODE_XADD: // not exists */ |
154 | 6.72k | } |
155 | 6.72k | return; |
156 | 6.72k | } |
157 | 18.2k | if (BPF_CLASS(opcode) == BPF_CLASS_ST || |
158 | 16.9k | BPF_CLASS(opcode) == BPF_CLASS_STX) { |
159 | 2.69k | if (!EBPF_MODE(MI->csh->mode)) { |
160 | | // cBPF has only one case - st* M[k] |
161 | 147 | push_op_mmem(bpf, (uint32_t)MCOperand_getImm( |
162 | 147 | MCInst_getOperand(MI, 0))); |
163 | 147 | return; |
164 | 147 | } |
165 | | /* eBPF has two cases: |
166 | | * - st [dst + off], src |
167 | | * - xadd [dst + off], src |
168 | | * they have same form of operands. |
169 | | */ |
170 | 2.54k | op = MCInst_getOperand(MI, 0); |
171 | 2.54k | op2 = MCInst_getOperand(MI, 1); |
172 | 2.54k | push_op_mem(bpf, MCOperand_getReg(op), |
173 | 2.54k | (uint32_t)MCOperand_getImm(op2), true, false); |
174 | | |
175 | 2.54k | op = MCInst_getOperand(MI, 2); |
176 | 2.54k | if (MCOperand_isImm(op)) |
177 | 1.20k | push_op_imm(bpf, MCOperand_getImm(op), false); |
178 | 1.33k | else if (MCOperand_isReg(op)) |
179 | 1.33k | push_op_reg(bpf, MCOperand_getReg(op), CS_AC_READ); |
180 | 2.54k | return; |
181 | 2.69k | } |
182 | | |
183 | 15.5k | { |
184 | 15.5k | const bool is_jmp32 = EBPF_MODE(MI->csh->mode) && |
185 | 10.5k | (BPF_CLASS(opcode) == BPF_CLASS_JMP32); |
186 | 15.5k | if (BPF_CLASS(opcode) == BPF_CLASS_JMP || is_jmp32) { |
187 | 24.3k | for (size_t i = 0; i < mc_op_count; i++) { |
188 | 17.6k | op = MCInst_getOperand(MI, i); |
189 | 17.6k | if (MCOperand_isImm(op)) { |
190 | | /* Decide if we're using IMM or OFF here (and if OFF, then signed or unsigned): |
191 | | * |
192 | | * 1. any jump/jump32 + signed off (not including exit/call and ja on jump32) // eBPF |
193 | | * 2. exit/call/ja + k // eBPF |
194 | | * 3. ja + unsigned off // cBPF (cBPF programs can only jump forwards) |
195 | | * 4. any jump {x,k}, +jt, +jf // cBPF |
196 | | * */ |
197 | | |
198 | 12.2k | if ((BPF_OP(opcode) == BPF_JUMP_JA && |
199 | 595 | !is_jmp32) || |
200 | 11.7k | (!EBPF_MODE(MI->csh->mode) && |
201 | 2.68k | i >= 1) || |
202 | 9.67k | (EBPF_MODE(MI->csh->mode) && |
203 | 9.10k | i == 2)) |
204 | 7.18k | push_op_off( |
205 | 7.18k | bpf, |
206 | 7.18k | MCOperand_getImm(op), |
207 | 7.18k | EBPF_MODE( |
208 | 7.18k | MI->csh->mode)); |
209 | 5.11k | else |
210 | 5.11k | push_op_imm( |
211 | 5.11k | bpf, |
212 | 5.11k | MCOperand_getImm(op), |
213 | 5.11k | true); |
214 | 12.2k | } else if (MCOperand_isReg(op)) { |
215 | 5.38k | push_op_reg(bpf, MCOperand_getReg(op), |
216 | 5.38k | CS_AC_READ); |
217 | 5.38k | } |
218 | 17.6k | } |
219 | 6.68k | return; |
220 | 6.68k | } |
221 | 15.5k | } |
222 | | |
223 | 8.88k | if (!EBPF_MODE(MI->csh->mode)) { |
224 | | /* In cBPF mode, all registers in operands are accessed as read */ |
225 | 6.98k | for (size_t i = 0; i < mc_op_count; i++) { |
226 | 3.33k | op = MCInst_getOperand(MI, i); |
227 | 3.33k | if (MCOperand_isImm(op)) |
228 | 1.43k | push_op_imm(bpf, MCOperand_getImm(op), false); |
229 | 1.90k | else if (MCOperand_isReg(op)) |
230 | 1.90k | push_op_reg(bpf, MCOperand_getReg(op), |
231 | 1.90k | CS_AC_READ); |
232 | 3.33k | } |
233 | 3.65k | return; |
234 | 3.65k | } |
235 | | |
236 | | /* remain cases are: eBPF mode && ALU */ |
237 | | /* if (BPF_CLASS(opcode) == BPF_CLASS_ALU || BPF_CLASS(opcode) == BPF_CLASS_ALU64) */ |
238 | | |
239 | | /* We have three types: |
240 | | * 1. {l,b}e dst // dst = byteswap(dst) |
241 | | * 2. neg dst // dst = -dst |
242 | | * 3. <op> dst, {src_reg, imm} // dst = dst <op> src |
243 | | * so we can simply check the number of operands, |
244 | | * exactly one operand means we are in case 1. and 2., |
245 | | * otherwise in case 3. |
246 | | */ |
247 | 5.23k | if (mc_op_count == 1) { |
248 | 833 | op = MCInst_getOperand(MI, 0); |
249 | 833 | push_op_reg(bpf, MCOperand_getReg(op), |
250 | 833 | CS_AC_READ | CS_AC_WRITE); |
251 | 4.40k | } else { // if (mc_op_count == 2) |
252 | 4.40k | op = MCInst_getOperand(MI, 0); |
253 | 4.40k | push_op_reg(bpf, MCOperand_getReg(op), |
254 | 4.40k | CS_AC_READ | CS_AC_WRITE); |
255 | | |
256 | 4.40k | op = MCInst_getOperand(MI, 1); |
257 | 4.40k | if (MCOperand_isImm(op)) |
258 | 4.07k | push_op_imm(bpf, MCOperand_getImm(op), false); |
259 | 327 | else if (MCOperand_isReg(op)) |
260 | 327 | push_op_reg(bpf, MCOperand_getReg(op), CS_AC_READ); |
261 | 4.40k | } |
262 | 5.23k | } |
263 | | |
264 | | static void print_operand(MCInst *MI, struct SStream *O, const cs_bpf_op *op) |
265 | 43.7k | { |
266 | 43.7k | switch (op->type) { |
267 | 0 | case BPF_OP_INVALID: |
268 | 0 | SStream_concat(O, "invalid"); |
269 | 0 | break; |
270 | 15.2k | case BPF_OP_REG: |
271 | 15.2k | SStream_concat(O, BPF_reg_name((csh)MI->csh, op->reg)); |
272 | 15.2k | break; |
273 | 12.8k | case BPF_OP_IMM: |
274 | 12.8k | if (op->is_signed) |
275 | 5.11k | printInt32Hex(O, op->imm); |
276 | 7.75k | else |
277 | 7.75k | SStream_concat(O, "0x%" PRIx64, op->imm); |
278 | 12.8k | break; |
279 | 7.18k | case BPF_OP_OFF: |
280 | 7.18k | if (op->is_signed) |
281 | 4.78k | printInt16HexOffset(O, op->off); |
282 | 2.39k | else |
283 | 2.39k | SStream_concat(O, "+0x%" PRIx32, op->off); |
284 | 7.18k | break; |
285 | 7.56k | case BPF_OP_MEM: |
286 | 7.56k | SStream_concat(O, "["); |
287 | | |
288 | 7.56k | if (op->is_pkt && EBPF_MODE(MI->csh->mode)) { |
289 | 2.46k | SStream_concat(O, "skb"); |
290 | | |
291 | 2.46k | if (op->mem.base != BPF_REG_INVALID) |
292 | 1.29k | SStream_concat(O, "+%s", |
293 | 1.29k | BPF_reg_name((csh)MI->csh, |
294 | 1.29k | op->mem.base)); |
295 | 1.17k | else { |
296 | 1.17k | if (op->is_signed) |
297 | 1.17k | printInt32HexOffset(O, op->mem.disp); |
298 | 0 | else |
299 | 0 | SStream_concat(O, "+0x%" PRIx32, |
300 | 0 | op->mem.disp); |
301 | 1.17k | } |
302 | 5.09k | } else { |
303 | 5.09k | if (op->mem.base != BPF_REG_INVALID) |
304 | 4.23k | SStream_concat(O, BPF_reg_name((csh)MI->csh, |
305 | 4.23k | op->mem.base)); |
306 | 5.09k | if (op->mem.disp != 0) { |
307 | 4.72k | if (op->mem.base != BPF_REG_INVALID) { |
308 | | // if operation is signed, then it always uses off, not k |
309 | 3.94k | if (op->is_signed) |
310 | 3.27k | printInt16HexOffset( |
311 | 3.27k | O, op->mem.disp); |
312 | 664 | else if (op->is_pkt) |
313 | 664 | SStream_concat(O, "+0x%" PRIx32, |
314 | 664 | op->mem.disp); |
315 | 0 | else |
316 | 0 | SStream_concat(O, "+0x%" PRIx16, |
317 | 0 | op->mem.disp); |
318 | 3.94k | } else |
319 | 787 | SStream_concat(O, "0x%" PRIx32, |
320 | 787 | op->mem.disp); |
321 | 4.72k | } |
322 | | |
323 | 5.09k | if (op->mem.base == BPF_REG_INVALID && |
324 | 860 | op->mem.disp == 0) |
325 | 73 | SStream_concat(O, "0x0"); |
326 | 5.09k | } |
327 | | |
328 | 7.56k | SStream_concat(O, "]"); |
329 | 7.56k | break; |
330 | 361 | case BPF_OP_MMEM: |
331 | 361 | SStream_concat(O, "m[0x%x]", op->mmem); |
332 | 361 | break; |
333 | 213 | case BPF_OP_MSH: |
334 | 213 | SStream_concat(O, "4*([0x%x]&0xf)", op->msh); |
335 | 213 | break; |
336 | 237 | case BPF_OP_EXT: |
337 | 237 | switch (op->ext) { |
338 | 237 | case BPF_EXT_LEN: |
339 | 237 | SStream_concat(O, "#len"); |
340 | 237 | break; |
341 | 237 | } |
342 | 237 | break; |
343 | 43.7k | } |
344 | 43.7k | } |
345 | | |
346 | | /* |
347 | | * 1. human readable mnemonic |
348 | | * 2. set pubOpcode (BPF_INSN_*) |
349 | | * 3. set detail->bpf.operands |
350 | | * */ |
351 | | void BPF_printInst(MCInst *MI, struct SStream *O, void *PrinterInfo) |
352 | 24.9k | { |
353 | 24.9k | cs_bpf bpf = { 0 }; |
354 | | |
355 | | /* set pubOpcode as instruction id */ |
356 | 24.9k | SStream_concat(O, BPF_insn_name((csh)MI->csh, MCInst_getOpcodePub(MI))); |
357 | 24.9k | convert_operands(MI, &bpf); |
358 | 68.7k | for (size_t i = 0; i < bpf.op_count; i++) { |
359 | 43.7k | if (i == 0) |
360 | 24.4k | SStream_concat(O, "\t"); |
361 | 19.2k | else |
362 | 19.2k | SStream_concat(O, ", "); |
363 | 43.7k | print_operand(MI, O, &bpf.operands[i]); |
364 | 43.7k | } |
365 | | |
366 | 24.9k | #ifndef CAPSTONE_DIET |
367 | 24.9k | if (detail_is_set(MI)) { |
368 | 24.9k | MI->flat_insn->detail->bpf = bpf; |
369 | 24.9k | } |
370 | 24.9k | #endif |
371 | 24.9k | } |