]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - opcodes/bpf-dis.c
Update year range in copyright notice of binutils files
[thirdparty/binutils-gdb.git] / opcodes / bpf-dis.c
1 /* bpf-dis.c - BPF disassembler.
2 Copyright (C) 2023-2024 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 {
55 fprintf (stream, _("\n\
56 The following BPF specific disassembler options are supported for use\n\
57 with the -M switch (multiple options should be separated by commas):\n"));
58 fprintf (stream, "\n");
59 fprintf (stream, _("\
60 pseudoc Use pseudo-c syntax.\n\
61 v1,v2,v3,v4,xbpf Version of the BPF ISA to use.\n\
62 hex,oct,dec Output numerical base for immediates.\n"));
63 }
64
65 /* Parse BPF specific command-line options. */
66
67 static void
68 parse_bpf_dis_option (const char *option)
69 {
70 if (strcmp (option, "pseudoc") == 0)
71 asm_dialect = BPF_DIALECT_PSEUDOC;
72 else if (strcmp (option, "v1") == 0)
73 asm_bpf_version = BPF_V1;
74 else if (strcmp (option, "v2") == 0)
75 asm_bpf_version = BPF_V2;
76 else if (strcmp (option, "v3") == 0)
77 asm_bpf_version = BPF_V3;
78 else if (strcmp (option, "v4") == 0)
79 asm_bpf_version = BPF_V4;
80 else if (strcmp (option, "xbpf") == 0)
81 asm_bpf_version = BPF_XBPF;
82 else if (strcmp (option, "hex") == 0)
83 asm_obase = 16;
84 else if (strcmp (option, "oct") == 0)
85 asm_obase = 8;
86 else if (strcmp (option, "dec") == 0)
87 asm_obase = 10;
88 else
89 /* xgettext:c-format */
90 opcodes_error_handler (_("unrecognized disassembler option: %s"), option);
91 }
92
93 static void
94 parse_bpf_dis_options (const char *opts_in)
95 {
96 char *opts = xstrdup (opts_in), *opt = opts, *opt_end = opts;
97
98 for ( ; opt_end != NULL; opt = opt_end + 1)
99 {
100 if ((opt_end = strchr (opt, ',')) != NULL)
101 *opt_end = 0;
102 parse_bpf_dis_option (opt);
103 }
104
105 free (opts);
106 }
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 {
114 const char *fmt
115 = (asm_dialect == BPF_DIALECT_NORMAL
116 ? "%%r%d"
117 : ((*(tag + 2) == 'w')
118 ? "w%d"
119 : "r%d"));
120
121 (*info->fprintf_styled_func) (info->stream, dis_style_register, fmt, regno);
122 }
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 {
131 int insn_size = 8, status;
132 bfd_byte insn_bytes[16];
133 bpf_insn_word word = 0;
134 const struct bpf_opcode *insn = NULL;
135 enum bpf_endian endian = (info->endian == BFD_ENDIAN_LITTLE
136 ? BPF_ENDIAN_LITTLE : BPF_ENDIAN_BIG);
137
138 /* Handle bpf-specific command-line options. */
139 if (info->disassembler_options != NULL)
140 {
141 parse_bpf_dis_options (info->disassembler_options);
142 /* Avoid repeteadly parsing the options. */
143 info->disassembler_options = NULL;
144 }
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 if (asm_bpf_version == -1 && info->section && info->section->owner)
151 {
152 struct bfd *abfd = info->section->owner;
153 Elf_Internal_Ehdr *header = elf_elfheader (abfd);
154 int cpu_version = header->e_flags & EF_BPF_CPUVER;
155
156 switch (cpu_version)
157 {
158 case 0: asm_bpf_version = BPF_V4; break;
159 case 1: asm_bpf_version = BPF_V1; break;
160 case 2: asm_bpf_version = BPF_V2; break;
161 case 3: asm_bpf_version = BPF_V3; break;
162 case 4: asm_bpf_version = BPF_V4; break;
163 case 0xf: asm_bpf_version = BPF_XBPF; break;
164 default:
165 /* xgettext:c-format */
166 opcodes_error_handler (_("unknown BPF CPU version %u\n"),
167 cpu_version);
168 break;
169 }
170 }
171
172 /* Print eight bytes per line. */
173 info->bytes_per_chunk = 1;
174 info->bytes_per_line = 8;
175
176 /* Read an instruction word. */
177 status = (*info->read_memory_func) (pc, insn_bytes, 8, info);
178 if (status != 0)
179 {
180 (*info->memory_error_func) (status, pc, info);
181 return -1;
182 }
183 word = (bpf_insn_word) bfd_getb64 (insn_bytes);
184
185 /* Try to match an instruction with it. */
186 insn = bpf_match_insn (word, endian, asm_bpf_version);
187
188 /* Print it out. */
189 if (insn)
190 {
191 const char *insn_tmpl
192 = asm_dialect == BPF_DIALECT_NORMAL ? insn->normal : insn->pseudoc;
193 const char *p = insn_tmpl;
194
195 /* Print the template contents completed with the instruction
196 operands. */
197 for (p = insn_tmpl; *p != '\0';)
198 {
199 switch (*p)
200 {
201 case ' ':
202 /* Single space prints to nothing. */
203 p += 1;
204 break;
205 case '%':
206 if (*(p + 1) == '%')
207 {
208 (*info->fprintf_styled_func) (info->stream, dis_style_text, "%%");
209 p += 2;
210 }
211 else if (*(p + 1) == 'w' || *(p + 1) == 'W')
212 {
213 /* %W prints to a single space. */
214 (*info->fprintf_styled_func) (info->stream, dis_style_text, " ");
215 p += 2;
216 }
217 else if (strncmp (p, "%dr", 3) == 0)
218 {
219 print_register (info, p, bpf_extract_dst (word, endian));
220 p += 3;
221 }
222 else if (strncmp (p, "%sr", 3) == 0)
223 {
224 print_register (info, p, bpf_extract_src (word, endian));
225 p += 3;
226 }
227 else if (strncmp (p, "%dw", 3) == 0)
228 {
229 print_register (info, p, bpf_extract_dst (word, endian));
230 p += 3;
231 }
232 else if (strncmp (p, "%sw", 3) == 0)
233 {
234 print_register (info, p, bpf_extract_src (word, endian));
235 p += 3;
236 }
237 else if (strncmp (p, "%i32", 4) == 0
238 || strncmp (p, "%d32", 4) == 0
239 || strncmp (p, "%I32", 4) == 0)
240 {
241 int32_t imm32 = bpf_extract_imm32 (word, endian);
242
243 if (p[1] == 'I')
244 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
245 "%s",
246 asm_obase != 10 || imm32 >= 0 ? "+" : "");
247 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
248 asm_obase == 10 ? "%" PRIi32
249 : asm_obase == 8 ? "%" PRIo32
250 : "0x%" PRIx32,
251 imm32);
252 p += 4;
253 }
254 else if (strncmp (p, "%o16", 4) == 0
255 || strncmp (p, "%d16", 4) == 0)
256 {
257 int16_t offset16 = bpf_extract_offset16 (word, endian);
258
259 if (p[1] == 'o')
260 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
261 "%s",
262 asm_obase != 10 || offset16 >= 0 ? "+" : "");
263 if (asm_obase == 16 || asm_obase == 8)
264 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
265 asm_obase == 8 ? "0%" PRIo16 : "0x%" PRIx16,
266 (uint16_t) offset16);
267 else
268 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
269 "%" PRIi16, offset16);
270 p += 4;
271 }
272 else if (strncmp (p, "%i64", 4) == 0)
273 {
274 bpf_insn_word word2 = 0;
275
276 status = (*info->read_memory_func) (pc + 8, insn_bytes + 8,
277 8, info);
278 if (status != 0)
279 {
280 (*info->memory_error_func) (status, pc + 8, info);
281 return -1;
282 }
283 word2 = (bpf_insn_word) bfd_getb64 (insn_bytes + 8);
284
285 (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
286 asm_obase == 10 ? "%" PRIi64
287 : asm_obase == 8 ? "0%" PRIo64
288 : "0x%" PRIx64,
289 bpf_extract_imm64 (word, word2, endian));
290 insn_size = 16;
291 p += 4;
292 }
293 else
294 {
295 /* xgettext:c-format */
296 opcodes_error_handler (_("# internal error, unknown tag in opcode template (%s)"),
297 insn_tmpl);
298 return -1;
299 }
300 break;
301 default:
302 /* Any other character is printed literally. */
303 (*info->fprintf_styled_func) (info->stream, dis_style_text, "%c", *p);
304 p += 1;
305 }
306 }
307 }
308 else
309 (*info->fprintf_styled_func) (info->stream, dis_style_text, "<unknown>");
310
311 return insn_size;
312 }