Home | History | Annotate | Line # | Download | only in opcodes
wasm32-dis.c revision 1.1.1.5
      1 /* Opcode printing code for the WebAssembly target
      2    Copyright (C) 2017-2024 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 #define BLOCK_TYPE_NONE              0x40
     39 #define BLOCK_TYPE_I32               0x7f
     40 #define BLOCK_TYPE_I64               0x7e
     41 #define BLOCK_TYPE_F32               0x7d
     42 #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 {
    117   struct wasm32_private_data *private = info->private_data;
    118 
    119   while (opts != NULL)
    120     {
    121       if (startswith (opts, "registers"))
    122         private->print_registers = true;
    123       else if (startswith (opts, "globals"))
    124         private->print_well_known_globals = true;
    125 
    126       opts = strchr (opts, ',');
    127       if (opts)
    128         opts++;
    129     }
    130 }
    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 {
    140   struct wasm32_private_data *private_data = info->private_data;
    141 
    142   if (sym == NULL)
    143     return false;
    144 
    145   if (strcmp(sym->section->name, "*ABS*") == 0)
    146     return false;
    147 
    148   if (private_data && private_data->section_prefix != NULL
    149       && strncmp (sym->section->name, private_data->section_prefix,
    150                   strlen (private_data->section_prefix)))
    151     return false;
    152 
    153   return true;
    154 }
    155 
    156 /* Initialize the disassembler structures for INFO.  */
    157 
    158 void
    159 disassemble_init_wasm32 (struct disassemble_info *info)
    160 {
    161   if (info->private_data == NULL)
    162     {
    163       static struct wasm32_private_data private;
    164 
    165       private.print_registers = false;
    166       private.print_well_known_globals = false;
    167       private.section_prefix = NULL;
    168 
    169       info->private_data = &private;
    170     }
    171 
    172   if (info->disassembler_options)
    173     {
    174       parse_wasm32_disassembler_options (info, info->disassembler_options);
    175 
    176       info->disassembler_options = NULL;
    177     }
    178 
    179   info->symbol_is_valid = wasm32_symbol_is_valid;
    180 }
    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 {
    196   uint64_t result = 0;
    197   unsigned int num_read = 0;
    198   unsigned int shift = 0;
    199   unsigned char byte = 0;
    200   unsigned char lost, mask;
    201   int status = 1;
    202 
    203   while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
    204     {
    205       num_read++;
    206 
    207       if (shift < CHAR_BIT * sizeof (result))
    208 	{
    209 	  result |= ((uint64_t) (byte & 0x7f)) << shift;
    210 	  /* These bits overflowed.  */
    211 	  lost = byte ^ (result >> shift);
    212 	  /* And this is the mask of possible overflow bits.  */
    213 	  mask = 0x7f ^ ((uint64_t) 0x7f << shift >> shift);
    214 	  shift += 7;
    215 	}
    216       else
    217 	{
    218 	  lost = byte;
    219 	  mask = 0x7f;
    220 	}
    221       if ((lost & mask) != (sign && (int64_t) result < 0 ? mask : 0))
    222 	status |= 2;
    223 
    224       if ((byte & 0x80) == 0)
    225 	{
    226 	  status &= ~1;
    227 	  if (sign && shift < CHAR_BIT * sizeof (result) && (byte & 0x40))
    228 	    result |= -((uint64_t) 1 << shift);
    229 	  break;
    230 	}
    231     }
    232 
    233   if (length_return != NULL)
    234     *length_return = num_read;
    235   if (error_return != NULL)
    236     *error_return = status != 0;
    237 
    238   return result;
    239 }
    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 {
    247   bfd_byte buf[4];
    248 
    249   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    250     return -1;
    251 
    252   floatformat_to_double (&floatformat_ieee_single_little, buf,
    253                          value);
    254 
    255   return sizeof (buf);
    256 }
    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 {
    264   bfd_byte buf[8];
    265 
    266   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    267     return -1;
    268 
    269   floatformat_to_double (&floatformat_ieee_double_little, buf,
    270                          value);
    271 
    272   return sizeof (buf);
    273 }
    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 {
    280   unsigned char opcode;
    281   struct wasm32_opcode_s *op;
    282   bfd_byte buffer[16];
    283   void *stream = info->stream;
    284   fprintf_ftype prin = info->fprintf_func;
    285   struct wasm32_private_data *private_data = info->private_data;
    286   uint64_t val;
    287   int len;
    288   unsigned int bytes_read;
    289   bool error;
    290 
    291   if (info->read_memory_func (pc, buffer, 1, info))
    292     return -1;
    293 
    294   opcode = buffer[0];
    295 
    296   for (op = wasm32_opcodes; op->name; op++)
    297     if (op->opcode == opcode)
    298       break;
    299 
    300   if (!op->name)
    301     {
    302       prin (stream, "\t.byte 0x%02x\n", buffer[0]);
    303       return 1;
    304     }
    305 
    306   len = 1;
    307 
    308   prin (stream, "\t");
    309   prin (stream, "%s", op->name);
    310 
    311   if (op->clas == wasm_typed)
    312     {
    313       val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, false);
    314       if (error)
    315 	return -1;
    316       len += bytes_read;
    317       switch (val)
    318 	{
    319 	case BLOCK_TYPE_NONE:
    320 	  prin (stream, "[]");
    321 	  break;
    322 	case BLOCK_TYPE_I32:
    323 	  prin (stream, "[i]");
    324 	  break;
    325 	case BLOCK_TYPE_I64:
    326 	  prin (stream, "[l]");
    327 	  break;
    328 	case BLOCK_TYPE_F32:
    329 	  prin (stream, "[f]");
    330 	  break;
    331 	case BLOCK_TYPE_F64:
    332 	  prin (stream, "[d]");
    333 	  break;
    334 	default:
    335 	  return -1;
    336 	}
    337     }
    338 
    339   switch (op->clas)
    340     {
    341     case wasm_special:
    342     case wasm_eqz:
    343     case wasm_binary:
    344     case wasm_unary:
    345     case wasm_conv:
    346     case wasm_relational:
    347     case wasm_drop:
    348     case wasm_signature:
    349     case wasm_call_import:
    350     case wasm_typed:
    351     case wasm_select:
    352       break;
    353 
    354     case wasm_break_table:
    355       {
    356 	uint32_t target_count, i;
    357 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    358 				false);
    359 	target_count = val;
    360 	if (error || target_count != val || target_count == (uint32_t) -1)
    361 	  return -1;
    362 	len += bytes_read;
    363 	prin (stream, " %u", target_count);
    364 	for (i = 0; i < target_count + 1; i++)
    365 	  {
    366 	    uint32_t target;
    367 	    val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    368 				    false);
    369 	    target = val;
    370 	    if (error || target != val)
    371 	      return -1;
    372 	    len += bytes_read;
    373 	    prin (stream, " %u", target);
    374 	  }
    375       }
    376       break;
    377 
    378     case wasm_break:
    379     case wasm_break_if:
    380       {
    381 	uint32_t depth;
    382 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    383 				false);
    384 	depth = val;
    385 	if (error || depth != val)
    386 	  return -1;
    387 	len += bytes_read;
    388 	prin (stream, " %u", depth);
    389       }
    390       break;
    391 
    392     case wasm_return:
    393       break;
    394 
    395     case wasm_constant_i32:
    396     case wasm_constant_i64:
    397       val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, true);
    398       if (error)
    399 	return -1;
    400       len += bytes_read;
    401       prin (stream, " %" PRId64, val);
    402       break;
    403 
    404     case wasm_constant_f32:
    405       {
    406 	double fconstant;
    407 	int ret;
    408 	/* This appears to be the best we can do, even though we're
    409 	   using host doubles for WebAssembly floats.  */
    410 	ret = read_f32 (&fconstant, pc + len, info);
    411 	if (ret < 0)
    412 	  return -1;
    413 	len += ret;
    414 	prin (stream, " %.9g", fconstant);
    415       }
    416       break;
    417 
    418     case wasm_constant_f64:
    419       {
    420 	double fconstant;
    421 	int ret;
    422 	ret = read_f64 (&fconstant, pc + len, info);
    423 	if (ret < 0)
    424 	  return -1;
    425 	len += ret;
    426 	prin (stream, " %.17g", fconstant);
    427       }
    428       break;
    429 
    430     case wasm_call:
    431       {
    432 	uint32_t function_index;
    433 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    434 				false);
    435 	function_index = val;
    436 	if (error || function_index != val)
    437 	  return -1;
    438 	len += bytes_read;
    439 	prin (stream, " ");
    440 	private_data->section_prefix = ".space.function_index";
    441 	(*info->print_address_func) ((bfd_vma) function_index, info);
    442 	private_data->section_prefix = NULL;
    443       }
    444       break;
    445 
    446     case wasm_call_indirect:
    447       {
    448 	uint32_t type_index, xtra_index;
    449 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    450 				false);
    451 	type_index = val;
    452 	if (error || type_index != val)
    453 	  return -1;
    454 	len += bytes_read;
    455 	prin (stream, " %u", type_index);
    456 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    457 				false);
    458 	xtra_index = val;
    459 	if (error || xtra_index != val)
    460 	  return -1;
    461 	len += bytes_read;
    462 	prin (stream, " %u", xtra_index);
    463       }
    464       break;
    465 
    466     case wasm_get_local:
    467     case wasm_set_local:
    468     case wasm_tee_local:
    469       {
    470 	uint32_t local_index;
    471 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    472 				false);
    473 	local_index = val;
    474 	if (error || local_index != val)
    475 	  return -1;
    476 	len += bytes_read;
    477 	prin (stream, " %u", local_index);
    478 	if (strcmp (op->name + 4, "local") == 0)
    479 	  {
    480 	    static const char *locals[] =
    481 	      {
    482 		"$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0",
    483 		"$rp", "$fp", "$sp",
    484 		"$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
    485 		"$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7",
    486 		"$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
    487 	      };
    488 	    if (private_data->print_registers
    489 		&& local_index < ARRAY_SIZE (locals))
    490 	      prin (stream, " <%s>", locals[local_index]);
    491 	  }
    492 	else
    493 	  {
    494 	    static const char *globals[] =
    495 	      {
    496 		"$got", "$plt", "$gpo"
    497 	      };
    498 	    if (private_data->print_well_known_globals
    499 		&& local_index < ARRAY_SIZE (globals))
    500 	      prin (stream, " <%s>", globals[local_index]);
    501 	  }
    502       }
    503       break;
    504 
    505     case wasm_grow_memory:
    506     case wasm_current_memory:
    507       {
    508 	uint32_t reserved_size;
    509 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    510 				false);
    511 	reserved_size = val;
    512 	if (error || reserved_size != val)
    513 	  return -1;
    514 	len += bytes_read;
    515 	prin (stream, " %u", reserved_size);
    516       }
    517       break;
    518 
    519     case wasm_load:
    520     case wasm_store:
    521       {
    522 	uint32_t flags, offset;
    523 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    524 				false);
    525 	flags = val;
    526 	if (error || flags != val)
    527 	  return -1;
    528 	len += bytes_read;
    529 	val = wasm_read_leb128 (pc + len, info, &error, &bytes_read,
    530 				false);
    531 	offset = val;
    532 	if (error || offset != val)
    533 	  return -1;
    534 	len += bytes_read;
    535 	prin (stream, " a=%u %u", flags, offset);
    536       }
    537       break;
    538     }
    539   return len;
    540 }
    541 
    542 /* Print valid disassembler options to STREAM.  */
    543 
    544 void
    545 print_wasm32_disassembler_options (FILE *stream)
    546 {
    547   unsigned int i, max_len = 0;
    548 
    549   fprintf (stream, _("\
    550 The following WebAssembly-specific disassembler options are supported for use\n\
    551 with the -M switch:\n"));
    552 
    553   for (i = 0; i < ARRAY_SIZE (options); i++)
    554     {
    555       unsigned int len = strlen (options[i].name);
    556 
    557       if (max_len < len)
    558 	max_len = len;
    559     }
    560 
    561   for (i = 0, max_len++; i < ARRAY_SIZE (options); i++)
    562     fprintf (stream, "  %s%*c %s\n",
    563 	     options[i].name,
    564 	     (int)(max_len - strlen (options[i].name)), ' ',
    565 	     _(options[i].description));
    566 }
    567