Coverage Report

Created: 2025-07-08 11:15

/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
129
{
114
129
  const char *fmt
115
129
    = (asm_dialect == BPF_DIALECT_NORMAL
116
129
       ? "%%r%d"
117
129
       : ((*(tag + 2) == 'w')
118
0
          ? "w%d"
119
0
          : "r%d"));
120
121
129
  (*info->fprintf_styled_func) (info->stream, dis_style_register, fmt, regno);
122
129
}
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
499
{
131
499
  int insn_size = 8, status;
132
499
  bfd_byte insn_bytes[16];
133
499
  bpf_insn_word word = 0;
134
499
  const struct bpf_opcode *insn = NULL;
135
499
  enum bpf_endian endian = (info->endian == BFD_ENDIAN_LITTLE
136
499
                            ? BPF_ENDIAN_LITTLE : BPF_ENDIAN_BIG);
137
138
  /* Handle bpf-specific command-line options.  */
139
499
  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
499
  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
499
  info->bytes_per_chunk = 1;
174
499
  info->bytes_per_line = 8;
175
176
  /* Read an instruction word.  */
177
499
  status = (*info->read_memory_func) (pc, insn_bytes, 8, info);
178
499
  if (status != 0)
179
9
    {
180
9
      (*info->memory_error_func) (status, pc, info);
181
9
      return -1;
182
9
    }
183
490
  word = (bpf_insn_word) bfd_getb64 (insn_bytes);
184
185
  /* Try to match an instruction with it.  */
186
490
  insn = bpf_match_insn (word, endian, asm_bpf_version);
187
188
  /* Print it out.  */
189
490
  if (insn)
190
104
    {
191
104
      const char *insn_tmpl
192
104
        = asm_dialect == BPF_DIALECT_NORMAL ? insn->normal : insn->pseudoc;
193
104
      const char *p = insn_tmpl;
194
195
      /* Print the template contents completed with the instruction
196
         operands.  */
197
1.30k
      for (p = insn_tmpl; *p != '\0';)
198
1.20k
        {
199
1.20k
          switch (*p)
200
1.20k
            {
201
260
            case ' ':
202
              /* Single space prints to nothing.  */
203
260
              p += 1;
204
260
              break;
205
337
            case '%':
206
337
              if (*(p + 1) == '%')
207
0
                {
208
0
                  (*info->fprintf_styled_func) (info->stream, dis_style_text, "%%");
209
0
                  p += 2;
210
0
                }
211
337
              else if (*(p + 1) == 'w' || *(p + 1) == 'W')
212
104
                {
213
                  /* %W prints to a single space.  */
214
104
                  (*info->fprintf_styled_func) (info->stream, dis_style_text, " ");
215
104
                  p += 2;
216
104
                }
217
233
              else if (strncmp (p, "%dr", 3) == 0)
218
90
                {
219
90
                  print_register (info, p, bpf_extract_dst (word, endian));
220
90
                  p += 3;
221
90
                }
222
143
              else if (strncmp (p, "%sr", 3) == 0)
223
39
                {
224
39
                  print_register (info, p, bpf_extract_src (word, endian));
225
39
                  p += 3;
226
39
                }
227
104
              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
104
              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
104
              else if (strncmp (p, "%i32", 4) == 0
238
104
                       || strncmp (p, "%d32", 4) == 0
239
104
                       || strncmp (p, "%I32", 4) == 0)
240
63
                {
241
63
                  int32_t imm32 = bpf_extract_imm32 (word, endian);
242
243
63
                  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
63
                  (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
248
63
                                                asm_obase == 10 ? "%" PRIi32
249
63
                                                : asm_obase == 8 ? "%" PRIo32
250
0
                                                : "0x%" PRIx32,
251
63
                                                imm32);
252
63
                  p += 4;
253
63
                }
254
41
              else if (strncmp (p, "%o16", 4) == 0
255
41
                       || strncmp (p, "%d16", 4) == 0)
256
40
                {
257
40
                  int16_t offset16 = bpf_extract_offset16 (word, endian);
258
259
40
                  if (p[1] == 'o')
260
13
                    (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
261
13
                                                  "%s",
262
13
              asm_obase != 10 || offset16 >= 0 ? "+" : "");
263
40
                  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
40
                  else
268
40
                    (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
269
40
                                                  "%" PRIi16, offset16);
270
40
                  p += 4;
271
40
                }
272
1
              else if (strncmp (p, "%i64", 4) == 0)
273
1
                {
274
1
                  bpf_insn_word word2 = 0;
275
276
1
                  status = (*info->read_memory_func) (pc + 8, insn_bytes + 8,
277
1
                                                          8, info);
278
1
                  if (status != 0)
279
0
                    {
280
0
                      (*info->memory_error_func) (status, pc + 8, info);
281
0
                      return -1;
282
0
                    }
283
1
                  word2 = (bpf_insn_word) bfd_getb64 (insn_bytes + 8);
284
285
1
                  (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
286
1
                                                asm_obase == 10 ? "%" PRIi64
287
1
                                                : asm_obase == 8 ? "0%" PRIo64
288
0
                                                : "0x%" PRIx64,
289
1
                                                bpf_extract_imm64 (word, word2, endian));
290
1
                  insn_size = 16;
291
1
                  p += 4;
292
1
                }
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
337
              break;
301
605
            default:
302
              /* Any other character is printed literally.  */
303
605
              (*info->fprintf_styled_func) (info->stream, dis_style_text, "%c", *p);
304
605
              p += 1;
305
1.20k
            }
306
1.20k
        }
307
104
    }
308
386
  else
309
386
    (*info->fprintf_styled_func) (info->stream, dis_style_text, "<unknown>");
310
311
490
  return insn_size;
312
490
}