/src/binutils-gdb/opcodes/bpf-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* bpf-dis.c - BPF disassembler. |
2 | | Copyright (C) 2023-2025 Free Software Foundation, Inc. |
3 | | |
4 | | Contributed by Oracle Inc. |
5 | | |
6 | | This file is part of the GNU binutils. |
7 | | |
8 | | This is free software; you can redistribute them and/or modify them |
9 | | under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation; either version 3, or (at your option) |
11 | | any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, but |
14 | | WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | | General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program; see the file COPYING3. If not, |
20 | | see <http://www.gnu.org/licenses/>. */ |
21 | | |
22 | | #include "sysdep.h" |
23 | | #include "disassemble.h" |
24 | | #include "libiberty.h" |
25 | | #include "opintl.h" |
26 | | #include "opcode/bpf.h" |
27 | | #include "elf-bfd.h" |
28 | | #include "elf/bpf.h" |
29 | | |
30 | | #include <string.h> |
31 | | #include <inttypes.h> |
32 | | |
33 | | /* This disassembler supports two different syntaxes for BPF assembly. |
34 | | One is called "normal" and has the typical form for assembly |
35 | | languages, with mnemonics and the like. The other is called |
36 | | "pseudoc" and looks like C. */ |
37 | | |
38 | | enum bpf_dialect |
39 | | { |
40 | | BPF_DIALECT_NORMAL, |
41 | | BPF_DIALECT_PSEUDOC |
42 | | }; |
43 | | |
44 | | /* Global configuration for the disassembler. */ |
45 | | |
46 | | static enum bpf_dialect asm_dialect = BPF_DIALECT_NORMAL; |
47 | | static int asm_bpf_version = -1; |
48 | | static int asm_obase = 10; |
49 | | |
50 | | /* Print BPF specific command-line options. */ |
51 | | |
52 | | void |
53 | | print_bpf_disassembler_options (FILE *stream) |
54 | 0 | { |
55 | 0 | fprintf (stream, _("\n\ |
56 | 0 | The following BPF specific disassembler options are supported for use\n\ |
57 | 0 | with the -M switch (multiple options should be separated by commas):\n")); |
58 | 0 | fprintf (stream, "\n"); |
59 | 0 | fprintf (stream, _("\ |
60 | 0 | pseudoc Use pseudo-c syntax.\n\ |
61 | 0 | v1,v2,v3,v4,xbpf Version of the BPF ISA to use.\n\ |
62 | 0 | hex,oct,dec Output numerical base for immediates.\n")); |
63 | 0 | } |
64 | | |
65 | | /* Parse BPF specific command-line options. */ |
66 | | |
67 | | static void |
68 | | parse_bpf_dis_option (const char *option) |
69 | 0 | { |
70 | 0 | if (strcmp (option, "pseudoc") == 0) |
71 | 0 | asm_dialect = BPF_DIALECT_PSEUDOC; |
72 | 0 | else if (strcmp (option, "v1") == 0) |
73 | 0 | asm_bpf_version = BPF_V1; |
74 | 0 | else if (strcmp (option, "v2") == 0) |
75 | 0 | asm_bpf_version = BPF_V2; |
76 | 0 | else if (strcmp (option, "v3") == 0) |
77 | 0 | asm_bpf_version = BPF_V3; |
78 | 0 | else if (strcmp (option, "v4") == 0) |
79 | 0 | asm_bpf_version = BPF_V4; |
80 | 0 | else if (strcmp (option, "xbpf") == 0) |
81 | 0 | asm_bpf_version = BPF_XBPF; |
82 | 0 | else if (strcmp (option, "hex") == 0) |
83 | 0 | asm_obase = 16; |
84 | 0 | else if (strcmp (option, "oct") == 0) |
85 | 0 | asm_obase = 8; |
86 | 0 | else if (strcmp (option, "dec") == 0) |
87 | 0 | asm_obase = 10; |
88 | 0 | else |
89 | | /* xgettext:c-format */ |
90 | 0 | opcodes_error_handler (_("unrecognized disassembler option: %s"), option); |
91 | 0 | } |
92 | | |
93 | | static void |
94 | | parse_bpf_dis_options (const char *opts_in) |
95 | 0 | { |
96 | 0 | char *opts = xstrdup (opts_in), *opt = opts, *opt_end = opts; |
97 | |
|
98 | 0 | for ( ; opt_end != NULL; opt = opt_end + 1) |
99 | 0 | { |
100 | 0 | if ((opt_end = strchr (opt, ',')) != NULL) |
101 | 0 | *opt_end = 0; |
102 | 0 | parse_bpf_dis_option (opt); |
103 | 0 | } |
104 | |
|
105 | 0 | free (opts); |
106 | 0 | } |
107 | | |
108 | | /* Auxiliary function used in print_insn_bpf below. */ |
109 | | |
110 | | static void |
111 | | print_register (disassemble_info *info, |
112 | | const char *tag, uint8_t regno) |
113 | 658 | { |
114 | 658 | const char *fmt |
115 | 658 | = (asm_dialect == BPF_DIALECT_NORMAL |
116 | 658 | ? "%%r%d" |
117 | 658 | : ((*(tag + 2) == 'w') |
118 | 0 | ? "w%d" |
119 | 0 | : "r%d")); |
120 | | |
121 | 658 | (*info->fprintf_styled_func) (info->stream, dis_style_register, fmt, regno); |
122 | 658 | } |
123 | | |
124 | | /* Main entry point. |
125 | | Print one instruction from PC on INFO->STREAM. |
126 | | Return the size of the instruction (in bytes). */ |
127 | | |
128 | | int |
129 | | print_insn_bpf (bfd_vma pc, disassemble_info *info) |
130 | 1.82k | { |
131 | 1.82k | int insn_size = 8, status; |
132 | 1.82k | bfd_byte insn_bytes[16]; |
133 | 1.82k | bpf_insn_word word = 0; |
134 | 1.82k | const struct bpf_opcode *insn = NULL; |
135 | 1.82k | enum bpf_endian endian = (info->endian == BFD_ENDIAN_LITTLE |
136 | 1.82k | ? BPF_ENDIAN_LITTLE : BPF_ENDIAN_BIG); |
137 | | |
138 | | /* Handle bpf-specific command-line options. */ |
139 | 1.82k | if (info->disassembler_options != NULL) |
140 | 0 | { |
141 | 0 | parse_bpf_dis_options (info->disassembler_options); |
142 | | /* Avoid repeteadly parsing the options. */ |
143 | 0 | info->disassembler_options = NULL; |
144 | 0 | } |
145 | | |
146 | | /* Determine what version of the BPF ISA to use when disassembling. |
147 | | If the user didn't explicitly specify an ISA version, then derive |
148 | | it from the CPU Version flag in the ELF header. A CPU version of |
149 | | 0 in the header means "latest version". */ |
150 | 1.82k | if (asm_bpf_version == -1 && info->section && info->section->owner) |
151 | 1 | { |
152 | 1 | struct bfd *abfd = info->section->owner; |
153 | 1 | Elf_Internal_Ehdr *header = elf_elfheader (abfd); |
154 | 1 | int cpu_version = header->e_flags & EF_BPF_CPUVER; |
155 | | |
156 | 1 | switch (cpu_version) |
157 | 1 | { |
158 | 1 | case 0: asm_bpf_version = BPF_V4; break; |
159 | 0 | case 1: asm_bpf_version = BPF_V1; break; |
160 | 0 | case 2: asm_bpf_version = BPF_V2; break; |
161 | 0 | case 3: asm_bpf_version = BPF_V3; break; |
162 | 0 | case 4: asm_bpf_version = BPF_V4; break; |
163 | 0 | case 0xf: asm_bpf_version = BPF_XBPF; break; |
164 | 0 | default: |
165 | | /* xgettext:c-format */ |
166 | 0 | opcodes_error_handler (_("unknown BPF CPU version %u\n"), |
167 | 0 | cpu_version); |
168 | 0 | break; |
169 | 1 | } |
170 | 1 | } |
171 | | |
172 | | /* Print eight bytes per line. */ |
173 | 1.82k | info->bytes_per_chunk = 1; |
174 | 1.82k | info->bytes_per_line = 8; |
175 | | |
176 | | /* Read an instruction word. */ |
177 | 1.82k | status = (*info->read_memory_func) (pc, insn_bytes, 8, info); |
178 | 1.82k | if (status != 0) |
179 | 14 | { |
180 | 14 | (*info->memory_error_func) (status, pc, info); |
181 | 14 | return -1; |
182 | 14 | } |
183 | 1.80k | word = (bpf_insn_word) bfd_getb64 (insn_bytes); |
184 | | |
185 | | /* Try to match an instruction with it. */ |
186 | 1.80k | insn = bpf_match_insn (word, endian, asm_bpf_version); |
187 | | |
188 | | /* Print it out. */ |
189 | 1.80k | if (insn) |
190 | 568 | { |
191 | 568 | const char *insn_tmpl |
192 | 568 | = asm_dialect == BPF_DIALECT_NORMAL ? insn->normal : insn->pseudoc; |
193 | 568 | const char *p = insn_tmpl; |
194 | | |
195 | | /* Print the template contents completed with the instruction |
196 | | operands. */ |
197 | 7.00k | for (p = insn_tmpl; *p != '\0';) |
198 | 6.43k | { |
199 | 6.43k | switch (*p) |
200 | 6.43k | { |
201 | 1.37k | case ' ': |
202 | | /* Single space prints to nothing. */ |
203 | 1.37k | p += 1; |
204 | 1.37k | break; |
205 | 1.80k | case '%': |
206 | 1.80k | if (*(p + 1) == '%') |
207 | 0 | { |
208 | 0 | (*info->fprintf_styled_func) (info->stream, dis_style_text, "%%"); |
209 | 0 | p += 2; |
210 | 0 | } |
211 | 1.80k | else if (*(p + 1) == 'w' || *(p + 1) == 'W') |
212 | 566 | { |
213 | | /* %W prints to a single space. */ |
214 | 566 | (*info->fprintf_styled_func) (info->stream, dis_style_text, " "); |
215 | 566 | p += 2; |
216 | 566 | } |
217 | 1.24k | else if (strncmp (p, "%dr", 3) == 0) |
218 | 453 | { |
219 | 453 | print_register (info, p, bpf_extract_dst (word, endian)); |
220 | 453 | p += 3; |
221 | 453 | } |
222 | 788 | else if (strncmp (p, "%sr", 3) == 0) |
223 | 205 | { |
224 | 205 | print_register (info, p, bpf_extract_src (word, endian)); |
225 | 205 | p += 3; |
226 | 205 | } |
227 | 583 | else if (strncmp (p, "%dw", 3) == 0) |
228 | 0 | { |
229 | 0 | print_register (info, p, bpf_extract_dst (word, endian)); |
230 | 0 | p += 3; |
231 | 0 | } |
232 | 583 | else if (strncmp (p, "%sw", 3) == 0) |
233 | 0 | { |
234 | 0 | print_register (info, p, bpf_extract_src (word, endian)); |
235 | 0 | p += 3; |
236 | 0 | } |
237 | 583 | else if (strncmp (p, "%i32", 4) == 0 |
238 | 583 | || strncmp (p, "%d32", 4) == 0 |
239 | 583 | || strncmp (p, "%I32", 4) == 0) |
240 | 352 | { |
241 | 352 | int32_t imm32 = bpf_extract_imm32 (word, endian); |
242 | | |
243 | 352 | if (p[1] == 'I') |
244 | 0 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
245 | 0 | "%s", |
246 | 0 | asm_obase != 10 || imm32 >= 0 ? "+" : ""); |
247 | 352 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
248 | 352 | asm_obase == 10 ? "%" PRIi32 |
249 | 352 | : asm_obase == 8 ? "%" PRIo32 |
250 | 0 | : "0x%" PRIx32, |
251 | 352 | imm32); |
252 | 352 | p += 4; |
253 | 352 | } |
254 | 231 | else if (strncmp (p, "%o16", 4) == 0 |
255 | 231 | || strncmp (p, "%d16", 4) == 0) |
256 | 218 | { |
257 | 218 | int16_t offset16 = bpf_extract_offset16 (word, endian); |
258 | | |
259 | 218 | if (p[1] == 'o') |
260 | 69 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
261 | 69 | "%s", |
262 | 69 | asm_obase != 10 || offset16 >= 0 ? "+" : ""); |
263 | 218 | if (asm_obase == 16 || asm_obase == 8) |
264 | 0 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
265 | 0 | asm_obase == 8 ? "0%" PRIo16 : "0x%" PRIx16, |
266 | 0 | (uint16_t) offset16); |
267 | 218 | else |
268 | 218 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
269 | 218 | "%" PRIi16, offset16); |
270 | 218 | p += 4; |
271 | 218 | } |
272 | 13 | else if (strncmp (p, "%i64", 4) == 0) |
273 | 13 | { |
274 | 13 | bpf_insn_word word2 = 0; |
275 | | |
276 | 13 | status = (*info->read_memory_func) (pc + 8, insn_bytes + 8, |
277 | 13 | 8, info); |
278 | 13 | if (status != 0) |
279 | 0 | { |
280 | 0 | (*info->memory_error_func) (status, pc + 8, info); |
281 | 0 | return -1; |
282 | 0 | } |
283 | 13 | word2 = (bpf_insn_word) bfd_getb64 (insn_bytes + 8); |
284 | | |
285 | 13 | (*info->fprintf_styled_func) (info->stream, dis_style_immediate, |
286 | 13 | asm_obase == 10 ? "%" PRIi64 |
287 | 13 | : asm_obase == 8 ? "0%" PRIo64 |
288 | 0 | : "0x%" PRIx64, |
289 | 13 | bpf_extract_imm64 (word, word2, endian)); |
290 | 13 | insn_size = 16; |
291 | 13 | p += 4; |
292 | 13 | } |
293 | 0 | else |
294 | 0 | { |
295 | | /* xgettext:c-format */ |
296 | 0 | opcodes_error_handler (_("# internal error, unknown tag in opcode template (%s)"), |
297 | 0 | insn_tmpl); |
298 | 0 | return -1; |
299 | 0 | } |
300 | 1.80k | break; |
301 | 3.25k | default: |
302 | | /* Any other character is printed literally. */ |
303 | 3.25k | (*info->fprintf_styled_func) (info->stream, dis_style_text, "%c", *p); |
304 | 3.25k | p += 1; |
305 | 6.43k | } |
306 | 6.43k | } |
307 | 568 | } |
308 | 1.24k | else |
309 | 1.24k | (*info->fprintf_styled_func) (info->stream, dis_style_text, "<unknown>"); |
310 | | |
311 | 1.80k | return insn_size; |
312 | 1.80k | } |