Coverage Report

Created: 2026-07-25 10:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/binutils-gdb/opcodes/wasm32-dis.c
Line
Count
Source
1
/* Opcode printing code for the WebAssembly target
2
   Copyright (C) 2017-2026 Free Software Foundation, Inc.
3
4
   This file is part of libopcodes.
5
6
   This library is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 3 of the License, or
9
   (at your option) any later version.
10
11
   It is distributed in the hope that it will be useful, but WITHOUT
12
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
14
   License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program; if not, write to the Free Software
18
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
19
   MA 02110-1301, USA.  */
20
21
#include "sysdep.h"
22
#include "disassemble.h"
23
#include "opintl.h"
24
#include "safe-ctype.h"
25
#include "floatformat.h"
26
#include "libiberty.h"
27
#include "elf-bfd.h"
28
#include "elf/internal.h"
29
#include "elf/wasm32.h"
30
#include <stdint.h>
31
32
#include <limits.h>
33
#ifndef CHAR_BIT
34
#define CHAR_BIT 8
35
#endif
36
37
/* Type names for blocks and signatures.  */
38
321
#define BLOCK_TYPE_NONE              0x40
39
236
#define BLOCK_TYPE_I32               0x7f
40
282
#define BLOCK_TYPE_I64               0x7e
41
111
#define BLOCK_TYPE_F32               0x7d
42
295
#define BLOCK_TYPE_F64               0x7c
43
44
enum wasm_class
45
{
46
  wasm_typed,
47
  wasm_special,
48
  wasm_break,
49
  wasm_break_if,
50
  wasm_break_table,
51
  wasm_return,
52
  wasm_call,
53
  wasm_call_import,
54
  wasm_call_indirect,
55
  wasm_get_local,
56
  wasm_set_local,
57
  wasm_tee_local,
58
  wasm_drop,
59
  wasm_constant_i32,
60
  wasm_constant_i64,
61
  wasm_constant_f32,
62
  wasm_constant_f64,
63
  wasm_unary,
64
  wasm_binary,
65
  wasm_conv,
66
  wasm_load,
67
  wasm_store,
68
  wasm_select,
69
  wasm_relational,
70
  wasm_eqz,
71
  wasm_current_memory,
72
  wasm_grow_memory,
73
  wasm_signature
74
};
75
76
struct wasm32_private_data
77
{
78
  bool print_registers;
79
  bool print_well_known_globals;
80
81
  /* Limit valid symbols to those with a given prefix.  */
82
  const char *section_prefix;
83
};
84
85
typedef struct
86
{
87
  const char *name;
88
  const char *description;
89
} wasm32_options_t;
90
91
static const wasm32_options_t options[] =
92
{
93
  { "registers", N_("Disassemble \"register\" names") },
94
  { "globals",   N_("Name well-known globals") },
95
};
96
97
#define WASM_OPCODE(opcode, name, intype, outtype, clas, signedness)     \
98
  { name, wasm_ ## clas, opcode },
99
100
struct wasm32_opcode_s
101
{
102
  const char *name;
103
  enum wasm_class clas;
104
  unsigned char opcode;
105
} wasm32_opcodes[] =
106
{
107
#include "opcode/wasm.h"
108
  { NULL, 0, 0 }
109
};
110
111
/* Parse the disassembler options in OPTS and initialize INFO.  */
112
113
static void
114
parse_wasm32_disassembler_options (struct disassemble_info *info,
115
                                   const char *opts)
116
0
{
117
0
  struct wasm32_private_data *private = info->private_data;
118
119
0
  while (opts != NULL)
120
0
    {
121
0
      if (startswith (opts, "registers"))
122
0
        private->print_registers = true;
123
0
      else if (startswith (opts, "globals"))
124
0
        private->print_well_known_globals = true;
125
126
0
      opts = strchr (opts, ',');
127
0
      if (opts)
128
0
        opts++;
129
0
    }
130
0
}
131
132
/* Check whether SYM is valid.  Special-case absolute symbols, which
133
   are unhelpful to print, and arguments to a "call" insn, which we
134
   want to be in a section matching a given prefix.  */
135
136
static bool
137
wasm32_symbol_is_valid (asymbol *sym,
138
                        struct disassemble_info *info)
139
0
{
140
0
  struct wasm32_private_data *private_data = info->private_data;
141
142
0
  if (sym == NULL)
143
0
    return false;
144
145
0
  if (strcmp(sym->section->name, "*ABS*") == 0)
146
0
    return false;
147
148
0
  if (private_data && private_data->section_prefix != NULL
149
0
      && strncmp (sym->section->name, private_data->section_prefix,
150
0
                  strlen (private_data->section_prefix)))
151
0
    return false;
152
153
0
  return true;
154
0
}
155
156
/* Initialize the disassembler structures for INFO.  */
157
158
void
159
disassemble_init_wasm32 (struct disassemble_info *info)
160
559
{
161
559
  if (info->private_data == NULL)
162
559
    {
163
559
      static struct wasm32_private_data private;
164
165
559
      private.print_registers = false;
166
559
      private.print_well_known_globals = false;
167
559
      private.section_prefix = NULL;
168
169
559
      info->private_data = &private;
170
559
    }
171
172
559
  if (info->disassembler_options)
173
0
    {
174
0
      parse_wasm32_disassembler_options (info, info->disassembler_options);
175
176
0
      info->disassembler_options = NULL;
177
0
    }
178
179
559
  info->symbol_is_valid = wasm32_symbol_is_valid;
180
559
}
181
182
/* Read an LEB128-encoded integer from INFO at address PC, reading one
183
   byte at a time.  Set ERROR_RETURN if no complete integer could be
184
   read, LENGTH_RETURN to the number oof bytes read (including bytes
185
   in incomplete numbers).  SIGN means interpret the number as
186
   SLEB128.  Unfortunately, this is a duplicate of wasm-module.c's
187
   wasm_read_leb128 ().  */
188
189
static uint64_t
190
wasm_read_leb128 (bfd_vma pc,
191
                  struct disassemble_info *info,
192
                  bool *error_return,
193
                  unsigned int *length_return,
194
                  bool sign)
195
27.6k
{
196
27.6k
  uint64_t result = 0;
197
27.6k
  unsigned int num_read = 0;
198
27.6k
  unsigned int shift = 0;
199
27.6k
  unsigned char byte = 0;
200
27.6k
  unsigned char lost, mask;
201
27.6k
  int status = 1;
202
203
39.8k
  while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
204
39.8k
    {
205
39.8k
      num_read++;
206
207
39.8k
      if (shift < CHAR_BIT * sizeof (result))
208
36.4k
  {
209
36.4k
    result |= ((uint64_t) (byte & 0x7f)) << shift;
210
    /* These bits overflowed.  */
211
36.4k
    lost = byte ^ (result >> shift);
212
    /* And this is the mask of possible overflow bits.  */
213
36.4k
    mask = 0x7f ^ ((uint64_t) 0x7f << shift >> shift);
214
36.4k
    shift += 7;
215
36.4k
  }
216
3.40k
      else
217
3.40k
  {
218
3.40k
    lost = byte;
219
3.40k
    mask = 0x7f;
220
3.40k
  }
221
39.8k
      if ((lost & mask) != (sign && (int64_t) result < 0 ? mask : 0))
222
2.58k
  status |= 2;
223
224
39.8k
      if ((byte & 0x80) == 0)
225
27.5k
  {
226
27.5k
    status &= ~1;
227
27.5k
    if (sign && shift < CHAR_BIT * sizeof (result) && (byte & 0x40))
228
674
      result |= -((uint64_t) 1 << shift);
229
27.5k
    break;
230
27.5k
  }
231
39.8k
    }
232
233
27.6k
  if (length_return != NULL)
234
27.6k
    *length_return = num_read;
235
27.6k
  if (error_return != NULL)
236
27.6k
    *error_return = status != 0;
237
238
27.6k
  return result;
239
27.6k
}
240
241
/* Read a 32-bit IEEE float from PC using INFO, convert it to a host
242
   double, and store it at VALUE.  */
243
244
static int
245
read_f32 (double *value, bfd_vma pc, struct disassemble_info *info)
246
449
{
247
449
  bfd_byte buf[4];
248
249
449
  if (info->read_memory_func (pc, buf, sizeof (buf), info))
250
2
    return -1;
251
252
447
  floatformat_to_double (&floatformat_ieee_single_little, buf,
253
447
                         value);
254
255
447
  return sizeof (buf);
256
449
}
257
258
/* Read a 64-bit IEEE float from PC using INFO, convert it to a host
259
   double, and store it at VALUE.  */
260
261
static int
262
read_f64 (double *value, bfd_vma pc, struct disassemble_info *info)
263
648
{
264
648
  bfd_byte buf[8];
265
266
648
  if (info->read_memory_func (pc, buf, sizeof (buf), info))
267
13
    return -1;
268
269
635
  floatformat_to_double (&floatformat_ieee_double_little, buf,
270
635
                         value);
271
272
635
  return sizeof (buf);
273
648
}
274
275
/* Main disassembly routine.  Disassemble insn at PC using INFO.  */
276
277
int
278
print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info)
279
122k
{
280
122k
  unsigned char opcode;
281
122k
  struct wasm32_opcode_s *op;
282
122k
  bfd_byte buffer[16];
283
122k
  void *stream = info->stream;
284
122k
  fprintf_ftype prin = info->fprintf_func;
285
122k
  struct wasm32_private_data *private_data = info->private_data;
286
122k
  uint64_t val;
287
122k
  int len;
288
122k
  unsigned int bytes_read;
289
122k
  bool error;
290
291
122k
  if (info->read_memory_func (pc, buffer, 1, info))
292
1
    return -1;
293
294
122k
  opcode = buffer[0];
295
296
8.42M
  for (op = wasm32_opcodes; op->name; op++)
297
8.39M
    if (op->opcode == opcode)
298
93.8k
      break;
299
300
122k
  if (!op->name)
301
28.6k
    {
302
28.6k
      prin (stream, "\t.byte 0x%02x\n", buffer[0]);
303
28.6k
      return 1;
304
28.6k
    }
305
306
93.8k
  len = 1;
307
308
93.8k
  prin (stream, "\t");
309
93.8k
  prin (stream, "%s", op->name);
310
311
93.8k
  if (op->clas == wasm_typed)
312
1.29k
    {
313
1.29k
      val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, false);
314
1.29k
      if (error)
315
17
  return -1;
316
1.27k
      len += bytes_read;
317
1.27k
      switch (val)
318
1.27k
  {
319
321
  case BLOCK_TYPE_NONE:
320
321
    prin (stream, "[]");
321
321
    break;
322
236
  case BLOCK_TYPE_I32:
323
236
    prin (stream, "[i]");
324
236
    break;
325
282
  case BLOCK_TYPE_I64:
326
282
    prin (stream, "[l]");
327
282
    break;
328
111
  case BLOCK_TYPE_F32:
329
111
    prin (stream, "[f]");
330
111
    break;
331
295
  case BLOCK_TYPE_F64:
332
295
    prin (stream, "[d]");
333
295
    break;
334
29
  default:
335
29
    return -1;
336
1.27k
  }
337
1.27k
    }
338
339
93.7k
  switch (op->clas)
340
93.7k
    {
341
46.6k
    case wasm_special:
342
46.8k
    case wasm_eqz:
343
59.5k
    case wasm_binary:
344
62.0k
    case wasm_unary:
345
66.5k
    case wasm_conv:
346
74.2k
    case wasm_relational:
347
74.2k
    case wasm_drop:
348
74.2k
    case wasm_signature:
349
74.2k
    case wasm_call_import:
350
75.5k
    case wasm_typed:
351
75.7k
    case wasm_select:
352
75.7k
      break;
353
354
420
    case wasm_break_table:
355
420
      {
356
420
  uint32_t target_count, i;
357
420
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
358
420
        false);
359
420
  target_count = val;
360
420
  if (error || target_count != val || target_count == (uint32_t) -1)
361
21
    return -1;
362
399
  len += bytes_read;
363
399
  prin (stream, " %u", target_count);
364
5.44k
  for (i = 0; i < target_count + 1; i++)
365
5.10k
    {
366
5.10k
      uint32_t target;
367
5.10k
      val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
368
5.10k
            false);
369
5.10k
      target = val;
370
5.10k
      if (error || target != val)
371
58
        return -1;
372
5.04k
      len += bytes_read;
373
5.04k
      prin (stream, " %u", target);
374
5.04k
    }
375
399
      }
376
341
      break;
377
378
580
    case wasm_break:
379
2.09k
    case wasm_break_if:
380
2.09k
      {
381
2.09k
  uint32_t depth;
382
2.09k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
383
2.09k
        false);
384
2.09k
  depth = val;
385
2.09k
  if (error || depth != val)
386
52
    return -1;
387
2.04k
  len += bytes_read;
388
2.04k
  prin (stream, " %u", depth);
389
2.04k
      }
390
0
      break;
391
392
1.19k
    case wasm_return:
393
1.19k
      break;
394
395
575
    case wasm_constant_i32:
396
1.15k
    case wasm_constant_i64:
397
1.15k
      val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, true);
398
1.15k
      if (error)
399
29
  return -1;
400
1.12k
      len += bytes_read;
401
1.12k
      prin (stream, " %" PRId64, val);
402
1.12k
      break;
403
404
449
    case wasm_constant_f32:
405
449
      {
406
449
  double fconstant;
407
449
  int ret;
408
  /* This appears to be the best we can do, even though we're
409
     using host doubles for WebAssembly floats.  */
410
449
  ret = read_f32 (&fconstant, pc + len, info);
411
449
  if (ret < 0)
412
2
    return -1;
413
447
  len += ret;
414
447
  prin (stream, " %.9g", fconstant);
415
447
      }
416
0
      break;
417
418
648
    case wasm_constant_f64:
419
648
      {
420
648
  double fconstant;
421
648
  int ret;
422
648
  ret = read_f64 (&fconstant, pc + len, info);
423
648
  if (ret < 0)
424
13
    return -1;
425
635
  len += ret;
426
635
  prin (stream, " %.17g", fconstant);
427
635
      }
428
0
      break;
429
430
1.49k
    case wasm_call:
431
1.49k
      {
432
1.49k
  uint32_t function_index;
433
1.49k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
434
1.49k
        false);
435
1.49k
  function_index = val;
436
1.49k
  if (error || function_index != val)
437
27
    return -1;
438
1.46k
  len += bytes_read;
439
1.46k
  prin (stream, " ");
440
1.46k
  private_data->section_prefix = ".space.function_index";
441
1.46k
  (*info->print_address_func) ((bfd_vma) function_index, info);
442
1.46k
  private_data->section_prefix = NULL;
443
1.46k
      }
444
0
      break;
445
446
678
    case wasm_call_indirect:
447
678
      {
448
678
  uint32_t type_index, xtra_index;
449
678
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
450
678
        false);
451
678
  type_index = val;
452
678
  if (error || type_index != val)
453
19
    return -1;
454
659
  len += bytes_read;
455
659
  prin (stream, " %u", type_index);
456
659
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
457
659
        false);
458
659
  xtra_index = val;
459
659
  if (error || xtra_index != val)
460
22
    return -1;
461
637
  len += bytes_read;
462
637
  prin (stream, " %u", xtra_index);
463
637
      }
464
0
      break;
465
466
1.27k
    case wasm_get_local:
467
2.30k
    case wasm_set_local:
468
2.94k
    case wasm_tee_local:
469
2.94k
      {
470
2.94k
  uint32_t local_index;
471
2.94k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
472
2.94k
        false);
473
2.94k
  local_index = val;
474
2.94k
  if (error || local_index != val)
475
47
    return -1;
476
2.89k
  len += bytes_read;
477
2.89k
  prin (stream, " %u", local_index);
478
2.89k
  if (strcmp (op->name + 4, "local") == 0)
479
1.93k
    {
480
1.93k
      static const char *locals[] =
481
1.93k
        {
482
1.93k
    "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0",
483
1.93k
    "$rp", "$fp", "$sp",
484
1.93k
    "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
485
1.93k
    "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7",
486
1.93k
    "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
487
1.93k
        };
488
1.93k
      if (private_data->print_registers
489
0
    && local_index < ARRAY_SIZE (locals))
490
0
        prin (stream, " <%s>", locals[local_index]);
491
1.93k
    }
492
960
  else
493
960
    {
494
960
      static const char *globals[] =
495
960
        {
496
960
    "$got", "$plt", "$gpo"
497
960
        };
498
960
      if (private_data->print_well_known_globals
499
0
    && local_index < ARRAY_SIZE (globals))
500
0
        prin (stream, " <%s>", globals[local_index]);
501
960
    }
502
2.89k
      }
503
0
      break;
504
505
1.72k
    case wasm_grow_memory:
506
1.98k
    case wasm_current_memory:
507
1.98k
      {
508
1.98k
  uint32_t reserved_size;
509
1.98k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
510
1.98k
        false);
511
1.98k
  reserved_size = val;
512
1.98k
  if (error || reserved_size != val)
513
33
    return -1;
514
1.95k
  len += bytes_read;
515
1.95k
  prin (stream, " %u", reserved_size);
516
1.95k
      }
517
0
      break;
518
519
2.53k
    case wasm_load:
520
4.93k
    case wasm_store:
521
4.93k
      {
522
4.93k
  uint32_t flags, offset;
523
4.93k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
524
4.93k
        false);
525
4.93k
  flags = val;
526
4.93k
  if (error || flags != val)
527
52
    return -1;
528
4.87k
  len += bytes_read;
529
4.87k
  val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
530
4.87k
        false);
531
4.87k
  offset = val;
532
4.87k
  if (error || offset != val)
533
42
    return -1;
534
4.83k
  len += bytes_read;
535
4.83k
  prin (stream, " a=%u %u", flags, offset);
536
4.83k
      }
537
0
      break;
538
93.7k
    }
539
93.3k
  return len;
540
93.7k
}
541
542
/* Print valid disassembler options to STREAM.  */
543
544
void
545
print_wasm32_disassembler_options (FILE *stream)
546
0
{
547
0
  unsigned int i, max_len = 0;
548
549
0
  fprintf (stream, _("\
550
0
The following WebAssembly-specific disassembler options are supported for use\n\
551
0
with the -M switch:\n"));
552
553
0
  for (i = 0; i < ARRAY_SIZE (options); i++)
554
0
    {
555
0
      unsigned int len = strlen (options[i].name);
556
557
0
      if (max_len < len)
558
0
  max_len = len;
559
0
    }
560
561
0
  for (i = 0, max_len++; i < ARRAY_SIZE (options); i++)
562
0
    fprintf (stream, "  %s%*c %s\n",
563
0
       options[i].name,
564
0
       (int)(max_len - strlen (options[i].name)), ' ',
565
       _(options[i].description));
566
0
}