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