xref: /netbsd-src/external/gpl3/binutils/dist/opcodes/bpf-dis.c (revision cb63e24e8d6aae7ddac1859a9015f48b1d8bd90e)
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
print_bpf_disassembler_options(FILE * stream)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
parse_bpf_dis_option(const char * option)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
parse_bpf_dis_options(const char * opts_in)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
print_register(disassemble_info * info,const char * tag,uint8_t regno)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
print_insn_bpf(bfd_vma pc,disassemble_info * info)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 }
313