Coverage Report

Created: 2026-09-14 08:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/binutils-gdb/opcodes/bpf-dis.c
Line
Count
Source
1
/* bpf-dis.c - BPF disassembler.
2
   Copyright (C) 2023-2026 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
2.50k
{
114
2.50k
  const char *fmt
115
2.50k
    = (asm_dialect == BPF_DIALECT_NORMAL
116
2.50k
       ? "%%r%d"
117
2.50k
       : ((*(tag + 2) == 'w')
118
0
          ? "w%d"
119
0
          : "r%d"));
120
121
2.50k
  (*info->fprintf_styled_func) (info->stream, dis_style_register, fmt, regno);
122
2.50k
}
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
5.86k
{
131
5.86k
  int insn_size = 8, status;
132
5.86k
  bfd_byte insn_bytes[16];
133
5.86k
  bpf_insn_word word = 0;
134
5.86k
  const struct bpf_opcode *insn = NULL;
135
5.86k
  enum bpf_endian endian = (info->endian == BFD_ENDIAN_LITTLE
136
5.86k
                            ? BPF_ENDIAN_LITTLE : BPF_ENDIAN_BIG);
137
138
  /* Handle bpf-specific command-line options.  */
139
5.86k
  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
5.86k
  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
5.86k
  info->bytes_per_chunk = 1;
174
5.86k
  info->bytes_per_line = 8;
175
176
  /* Read an instruction word.  */
177
5.86k
  status = (*info->read_memory_func) (pc, insn_bytes, 8, info);
178
5.86k
  if (status != 0)
179
38
    {
180
38
      (*info->memory_error_func) (status, pc, info);
181
38
      return -1;
182
38
    }
183
5.82k
  word = (bpf_insn_word) bfd_getb64 (insn_bytes);
184
185
  /* Try to match an instruction with it.  */
186
5.82k
  insn = bpf_match_insn (word, endian, asm_bpf_version);
187
188
  /* Print it out.  */
189
5.82k
  if (insn)
190
2.12k
    {
191
2.12k
      const char *insn_tmpl
192
2.12k
        = asm_dialect == BPF_DIALECT_NORMAL ? insn->normal : insn->pseudoc;
193
2.12k
      const char *p = insn_tmpl;
194
195
      /* Print the template contents completed with the instruction
196
         operands.  */
197
26.6k
      for (p = insn_tmpl; *p != '\0';)
198
24.4k
        {
199
24.4k
          switch (*p)
200
24.4k
            {
201
5.50k
            case ' ':
202
              /* Single space prints to nothing.  */
203
5.50k
              p += 1;
204
5.50k
              break;
205
6.81k
            case '%':
206
6.81k
              if (*(p + 1) == '%')
207
0
                {
208
0
                  (*info->fprintf_styled_func) (info->stream, dis_style_text, "%%");
209
0
                  p += 2;
210
0
                }
211
6.81k
              else if (*(p + 1) == 't')
212
0
                {
213
                  /* %t marks the end of a mnemonic and prints to nothing;
214
                     the %w or %W that follows prints the space.  */
215
0
                  p += 2;
216
0
                }
217
6.81k
              else if (*(p + 1) == 'w' || *(p + 1) == 'W')
218
2.11k
                {
219
                  /* %W prints to a single space.  */
220
2.11k
                  (*info->fprintf_styled_func) (info->stream, dis_style_text, " ");
221
2.11k
                  p += 2;
222
2.11k
                }
223
4.69k
              else if (strncmp (p, "%dr", 3) == 0
224
2.94k
                       || strncmp (p, "%dw", 3) == 0
225
2.94k
                       || strncmp (p, "%dR", 3) == 0)
226
1.74k
                {
227
1.74k
                  print_register (info, p, bpf_extract_dst (word, endian));
228
1.74k
                  p += 3;
229
1.74k
                }
230
2.94k
              else if (strncmp (p, "%sr", 3) == 0
231
2.19k
                       || strncmp (p, "%sw", 3) == 0
232
2.19k
                       || strncmp (p, "%sR", 3) == 0)
233
757
                {
234
757
                  print_register (info, p, bpf_extract_src (word, endian));
235
757
                  p += 3;
236
757
                }
237
2.19k
              else if (strncmp (p, "%i32", 4) == 0
238
1.03k
                       || strncmp (p, "%d32", 4) == 0
239
833
                       || strncmp (p, "%I32", 4) == 0)
240
1.35k
                {
241
1.35k
                  int32_t imm32 = bpf_extract_imm32 (word, endian);
242
243
1.35k
                  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
1.35k
                  (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
248
1.35k
                                                asm_obase == 10 ? "%" PRIi32
249
1.35k
                                                : asm_obase == 8 ? "%" PRIo32
250
0
                                                : "0x%" PRIx32,
251
1.35k
                                                imm32);
252
1.35k
                  p += 4;
253
1.35k
                }
254
833
              else if (strncmp (p, "%o16", 4) == 0
255
426
                       || strncmp (p, "%d16", 4) == 0)
256
775
                {
257
775
                  int16_t offset16 = bpf_extract_offset16 (word, endian);
258
259
775
                  if (p[1] == 'o')
260
407
                    (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
261
407
                                                  "%s",
262
407
              asm_obase != 10 || offset16 >= 0 ? "+" : "");
263
775
                  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
775
                  else
268
775
                    (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
269
775
                                                  "%" PRIi16, offset16);
270
775
                  p += 4;
271
775
                }
272
58
              else if (strncmp (p, "%i64", 4) == 0)
273
58
                {
274
58
                  bpf_insn_word word2 = 0;
275
276
58
                  status = (*info->read_memory_func) (pc + 8, insn_bytes + 8,
277
58
                                                          8, info);
278
58
                  if (status != 0)
279
1
                    {
280
1
                      (*info->memory_error_func) (status, pc + 8, info);
281
1
                      return -1;
282
1
                    }
283
57
                  word2 = (bpf_insn_word) bfd_getb64 (insn_bytes + 8);
284
285
57
                  (*info->fprintf_styled_func) (info->stream, dis_style_immediate,
286
57
                                                asm_obase == 10 ? "%" PRIi64
287
57
                                                : asm_obase == 8 ? "0%" PRIo64
288
0
                                                : "0x%" PRIx64,
289
57
                                                bpf_extract_imm64 (word, word2, endian));
290
57
                  insn_size = 16;
291
57
                  p += 4;
292
57
                }
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
6.80k
              break;
301
12.1k
            default:
302
              /* Any other character is printed literally.  */
303
12.1k
              (*info->fprintf_styled_func) (info->stream, dis_style_text, "%c", *p);
304
12.1k
              p += 1;
305
24.4k
            }
306
24.4k
        }
307
2.12k
    }
308
3.70k
  else
309
3.70k
    (*info->fprintf_styled_func) (info->stream, dis_style_text, "<unknown>");
310
311
5.82k
  return insn_size;
312
5.82k
}