(root)/
binutils-2.41/
opcodes/
wasm32-dis.c
       1  /* Opcode printing code for the WebAssembly target
       2     Copyright (C) 2017-2023 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  }