Home | History | Annotate | Line # | Download | only in opcodes
wasm32-dis.c revision 1.1.1.2
      1 /* Opcode printing code for the WebAssembly target
      2    Copyright (C) 2017-2019 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   bfd_boolean success = FALSE;
    196 
    197   while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
    198     {
    199       num_read++;
    200 
    201       result |= ((bfd_vma) (byte & 0x7f)) << shift;
    202 
    203       shift += 7;
    204       if ((byte & 0x80) == 0)
    205         {
    206           success = TRUE;
    207           break;
    208         }
    209     }
    210 
    211   if (length_return != NULL)
    212     *length_return = num_read;
    213   if (error_return != NULL)
    214     *error_return = ! success;
    215 
    216   if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40))
    217     result |= -((uint64_t) 1 << shift);
    218 
    219   return result;
    220 }
    221 
    222 /* Read a 32-bit IEEE float from PC using INFO, convert it to a host
    223    double, and store it at VALUE.  */
    224 
    225 static int
    226 read_f32 (double *value, bfd_vma pc, struct disassemble_info *info)
    227 {
    228   bfd_byte buf[4];
    229 
    230   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    231     return -1;
    232 
    233   floatformat_to_double (&floatformat_ieee_single_little, buf,
    234                          value);
    235 
    236   return sizeof (buf);
    237 }
    238 
    239 /* Read a 64-bit IEEE float from PC using INFO, convert it to a host
    240    double, and store it at VALUE.  */
    241 
    242 static int
    243 read_f64 (double *value, bfd_vma pc, struct disassemble_info *info)
    244 {
    245   bfd_byte buf[8];
    246 
    247   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    248     return -1;
    249 
    250   floatformat_to_double (&floatformat_ieee_double_little, buf,
    251                          value);
    252 
    253   return sizeof (buf);
    254 }
    255 
    256 /* Main disassembly routine.  Disassemble insn at PC using INFO.  */
    257 
    258 int
    259 print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info)
    260 {
    261   unsigned char opcode;
    262   struct wasm32_opcode_s *op;
    263   bfd_byte buffer[16];
    264   void *stream = info->stream;
    265   fprintf_ftype prin = info->fprintf_func;
    266   struct wasm32_private_data *private_data = info->private_data;
    267   long long constant = 0;
    268   double fconstant = 0.0;
    269   long flags = 0;
    270   long offset = 0;
    271   long depth = 0;
    272   long function_index = 0;
    273   long target_count = 0;
    274   long block_type = 0;
    275   int len = 1;
    276   int ret = 0;
    277   unsigned int bytes_read = 0;
    278   int i;
    279   const char *locals[] =
    280     {
    281       "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0",
    282       "$rp", "$fp", "$sp",
    283       "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
    284       "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7",
    285       "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
    286     };
    287   int nlocals = ARRAY_SIZE (locals);
    288   const char *globals[] =
    289     {
    290       "$got", "$plt", "$gpo"
    291     };
    292   int nglobals = ARRAY_SIZE (globals);
    293   bfd_boolean error = FALSE;
    294 
    295   if (info->read_memory_func (pc, buffer, 1, info))
    296     return -1;
    297 
    298   opcode = buffer[0];
    299 
    300   for (op = wasm32_opcodes; op->name; op++)
    301     if (op->opcode == opcode)
    302       break;
    303 
    304   if (!op->name)
    305     {
    306       prin (stream, "\t.byte 0x%02x\n", buffer[0]);
    307       return 1;
    308     }
    309   else
    310     {
    311       len = 1;
    312 
    313       prin (stream, "\t");
    314       prin (stream, "%s", op->name);
    315 
    316       if (op->clas == wasm_typed)
    317         {
    318           block_type = wasm_read_leb128
    319             (pc + len, info, &error, &bytes_read, FALSE);
    320           if (error)
    321             return -1;
    322           len += bytes_read;
    323           switch (block_type)
    324             {
    325             case BLOCK_TYPE_NONE:
    326               prin (stream, "[]");
    327               break;
    328             case BLOCK_TYPE_I32:
    329               prin (stream, "[i]");
    330               break;
    331             case BLOCK_TYPE_I64:
    332               prin (stream, "[l]");
    333               break;
    334             case BLOCK_TYPE_F32:
    335               prin (stream, "[f]");
    336               break;
    337             case BLOCK_TYPE_F64:
    338               prin (stream, "[d]");
    339               break;
    340             }
    341         }
    342 
    343       switch (op->clas)
    344         {
    345         case wasm_special:
    346         case wasm_eqz:
    347         case wasm_binary:
    348         case wasm_unary:
    349         case wasm_conv:
    350         case wasm_relational:
    351         case wasm_drop:
    352         case wasm_signature:
    353         case wasm_call_import:
    354         case wasm_typed:
    355         case wasm_select:
    356           break;
    357 
    358         case wasm_break_table:
    359           target_count = wasm_read_leb128
    360             (pc + len, info, &error, &bytes_read, FALSE);
    361           if (error)
    362             return -1;
    363           len += bytes_read;
    364           prin (stream, " %ld", target_count);
    365           for (i = 0; i < target_count + 1; i++)
    366             {
    367               long target = 0;
    368               target = wasm_read_leb128
    369                 (pc + len, info, &error, &bytes_read, FALSE);
    370               if (error)
    371                 return -1;
    372               len += bytes_read;
    373               prin (stream, " %ld", target);
    374             }
    375           break;
    376 
    377         case wasm_break:
    378         case wasm_break_if:
    379           depth = wasm_read_leb128
    380             (pc + len, info, &error, &bytes_read, FALSE);
    381           if (error)
    382             return -1;
    383           len += bytes_read;
    384           prin (stream, " %ld", depth);
    385           break;
    386 
    387         case wasm_return:
    388           break;
    389 
    390         case wasm_constant_i32:
    391         case wasm_constant_i64:
    392           constant = wasm_read_leb128
    393             (pc + len, info, &error, &bytes_read, TRUE);
    394           if (error)
    395             return -1;
    396           len += bytes_read;
    397           prin (stream, " %lld", constant);
    398           break;
    399 
    400         case wasm_constant_f32:
    401           /* This appears to be the best we can do, even though we're
    402              using host doubles for WebAssembly floats.  */
    403           ret = read_f32 (&fconstant, pc + len, info);
    404           if (ret < 0)
    405             return -1;
    406           len += ret;
    407 	  prin (stream, " %.9g", fconstant);
    408           break;
    409 
    410         case wasm_constant_f64:
    411           ret = read_f64 (&fconstant, pc + len, info);
    412           if (ret < 0)
    413             return -1;
    414           len += ret;
    415 	  prin (stream, " %.17g", fconstant);
    416           break;
    417 
    418         case wasm_call:
    419           function_index = wasm_read_leb128
    420             (pc + len, info, &error, &bytes_read, FALSE);
    421           if (error)
    422             return -1;
    423           len += bytes_read;
    424           prin (stream, " ");
    425           private_data->section_prefix = ".space.function_index";
    426           (*info->print_address_func) ((bfd_vma) function_index, info);
    427           private_data->section_prefix = NULL;
    428           break;
    429 
    430         case wasm_call_indirect:
    431           constant = wasm_read_leb128
    432             (pc + len, info, &error, &bytes_read, FALSE);
    433           if (error)
    434             return -1;
    435           len += bytes_read;
    436           prin (stream, " %lld", constant);
    437           constant = wasm_read_leb128
    438             (pc + len, info, &error, &bytes_read, FALSE);
    439           if (error)
    440             return -1;
    441           len += bytes_read;
    442           prin (stream, " %lld", constant);
    443           break;
    444 
    445         case wasm_get_local:
    446         case wasm_set_local:
    447         case wasm_tee_local:
    448           constant = wasm_read_leb128
    449             (pc + len, info, &error, &bytes_read, FALSE);
    450           if (error)
    451             return -1;
    452           len += bytes_read;
    453           prin (stream, " %lld", constant);
    454           if (strcmp (op->name + 4, "local") == 0)
    455             {
    456               if (private_data->print_registers
    457                   && constant >= 0 && constant < nlocals)
    458                 prin (stream, " <%s>", locals[constant]);
    459             }
    460           else
    461             {
    462               if (private_data->print_well_known_globals
    463                   && constant >= 0 && constant < nglobals)
    464                 prin (stream, " <%s>", globals[constant]);
    465             }
    466           break;
    467 
    468         case wasm_grow_memory:
    469         case wasm_current_memory:
    470           constant = wasm_read_leb128
    471             (pc + len, info, &error, &bytes_read, FALSE);
    472           if (error)
    473             return -1;
    474           len += bytes_read;
    475           prin (stream, " %lld", constant);
    476           break;
    477 
    478         case wasm_load:
    479         case wasm_store:
    480           flags = wasm_read_leb128
    481             (pc + len, info, &error, &bytes_read, FALSE);
    482           if (error)
    483             return -1;
    484           len += bytes_read;
    485           offset = wasm_read_leb128
    486             (pc + len, info, &error, &bytes_read, FALSE);
    487           if (error)
    488             return -1;
    489           len += bytes_read;
    490           prin (stream, " a=%ld %ld", flags, offset);
    491         }
    492     }
    493   return len;
    494 }
    495 
    496 /* Print valid disassembler options to STREAM.  */
    497 
    498 void
    499 print_wasm32_disassembler_options (FILE *stream)
    500 {
    501   unsigned int i, max_len = 0;
    502 
    503   fprintf (stream, _("\
    504 The following WebAssembly-specific disassembler options are supported for use\n\
    505 with the -M switch:\n"));
    506 
    507   for (i = 0; i < ARRAY_SIZE (options); i++)
    508     {
    509       unsigned int len = strlen (options[i].name);
    510 
    511       if (max_len < len)
    512 	max_len = len;
    513     }
    514 
    515   for (i = 0, max_len++; i < ARRAY_SIZE (options); i++)
    516     fprintf (stream, "  %s%*c %s\n",
    517 	     options[i].name,
    518 	     (int)(max_len - strlen (options[i].name)), ' ',
    519 	     _(options[i].description));
    520 }
    521