]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - opcodes/wasm32-dis.c
Add support for disassembling WebAssembly opcodes.
[thirdparty/binutils-gdb.git] / opcodes / wasm32-dis.c
diff --git a/opcodes/wasm32-dis.c b/opcodes/wasm32-dis.c
new file mode 100644 (file)
index 0000000..80e4ffe
--- /dev/null
@@ -0,0 +1,521 @@
+/* Opcode printing code for the WebAssembly target
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of libopcodes.
+
+   This library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   It is distributed in the hope that it will be useful, but WITHOUT
+   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+   License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+   MA 02110-1301, USA.  */
+
+#include "sysdep.h"
+#include "dis-asm.h"
+#include "opintl.h"
+#include "safe-ctype.h"
+#include "floatformat.h"
+#include <float.h>
+#include "libiberty.h"
+#include "elf-bfd.h"
+#include "elf/internal.h"
+#include "elf/wasm32.h"
+#include <stdint.h>
+
+/* Type names for blocks and signatures.  */
+#define BLOCK_TYPE_NONE              0x40
+#define BLOCK_TYPE_I32               0x7f
+#define BLOCK_TYPE_I64               0x7e
+#define BLOCK_TYPE_F32               0x7d
+#define BLOCK_TYPE_F64               0x7c
+
+enum wasm_class
+{
+  wasm_typed,
+  wasm_special,
+  wasm_break,
+  wasm_break_if,
+  wasm_break_table,
+  wasm_return,
+  wasm_call,
+  wasm_call_import,
+  wasm_call_indirect,
+  wasm_get_local,
+  wasm_set_local,
+  wasm_tee_local,
+  wasm_drop,
+  wasm_constant_i32,
+  wasm_constant_i64,
+  wasm_constant_f32,
+  wasm_constant_f64,
+  wasm_unary,
+  wasm_binary,
+  wasm_conv,
+  wasm_load,
+  wasm_store,
+  wasm_select,
+  wasm_relational,
+  wasm_eqz,
+  wasm_current_memory,
+  wasm_grow_memory,
+  wasm_signature
+};
+
+struct wasm32_private_data
+{
+  bfd_boolean print_registers;
+  bfd_boolean print_well_known_globals;
+
+  /* Limit valid symbols to those with a given prefix.  */
+  const char *section_prefix;
+};
+
+typedef struct
+{
+  const char *name;
+  const char *description;
+} wasm32_options_t;
+
+static const wasm32_options_t options[] =
+{
+  { "registers", N_("Disassemble \"register\" names") },
+  { "globals",   N_("Name well-known globals") },
+};
+
+#define WASM_OPCODE(opcode, name, intype, outtype, clas, signedness)     \
+  { name, wasm_ ## clas, opcode },
+
+struct wasm32_opcode_s
+{
+  const char *name;
+  enum wasm_class clas;
+  unsigned char opcode;
+} wasm32_opcodes[] =
+{
+#include "opcode/wasm.h"
+  { NULL, 0, 0 }
+};
+
+/* Parse the disassembler options in OPTS and initialize INFO.  */
+
+static void
+parse_wasm32_disassembler_options (struct disassemble_info *info,
+                                   const char *opts)
+{
+  struct wasm32_private_data *private = info->private_data;
+
+  while (opts != NULL)
+    {
+      if (CONST_STRNEQ (opts, "registers"))
+        private->print_registers = TRUE;
+      else if (CONST_STRNEQ (opts, "globals"))
+        private->print_well_known_globals = TRUE;
+
+      opts = strchr (opts, ',');
+      if (opts)
+        opts++;
+    }
+}
+
+/* Check whether SYM is valid.  Special-case absolute symbols, which
+   are unhelpful to print, and arguments to a "call" insn, which we
+   want to be in a section matching a given prefix.  */
+
+static bfd_boolean
+wasm32_symbol_is_valid (asymbol *sym,
+                        struct disassemble_info *info)
+{
+  struct wasm32_private_data *private_data = info->private_data;
+
+  if (sym == NULL)
+    return FALSE;
+
+  if (strcmp(sym->section->name, "*ABS*") == 0)
+    return FALSE;
+
+  if (private_data && private_data->section_prefix != NULL
+      && strncmp (sym->section->name, private_data->section_prefix,
+                  strlen (private_data->section_prefix)))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* Initialize the disassembler structures for INFO.  */
+
+void
+disassemble_init_wasm32 (struct disassemble_info *info)
+{
+  if (info->private_data == NULL)
+    {
+      static struct wasm32_private_data private;
+
+      private.print_registers = FALSE;
+      private.print_well_known_globals = FALSE;
+      private.section_prefix = NULL;
+
+      info->private_data = &private;
+    }
+
+  if (info->disassembler_options)
+    {
+      parse_wasm32_disassembler_options (info, info->disassembler_options);
+
+      info->disassembler_options = NULL;
+    }
+
+  info->symbol_is_valid = wasm32_symbol_is_valid;
+}
+
+/* Read an LEB128-encoded integer from INFO at address PC, reading one
+   byte at a time.  Set ERROR_RETURN if no complete integer could be
+   read, LENGTH_RETURN to the number oof bytes read (including bytes
+   in incomplete numbers).  SIGN means interpret the number as
+   SLEB128.  Unfortunately, this is a duplicate of wasm-module.c's
+   wasm_read_leb128 ().  */
+
+static uint64_t
+wasm_read_leb128 (bfd_vma                   pc,
+                  struct disassemble_info * info,
+                  bfd_boolean *             error_return,
+                  unsigned int *            length_return,
+                  bfd_boolean               sign)
+{
+  uint64_t result = 0;
+  unsigned int num_read = 0;
+  unsigned int shift = 0;
+  unsigned char byte = 0;
+  bfd_boolean success = FALSE;
+
+  while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
+    {
+      num_read++;
+
+      result |= ((bfd_vma) (byte & 0x7f)) << shift;
+
+      shift += 7;
+      if ((byte & 0x80) == 0)
+        {
+          success = TRUE;
+          break;
+        }
+    }
+
+  if (length_return != NULL)
+    *length_return = num_read;
+  if (error_return != NULL)
+    *error_return = ! success;
+
+  if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40))
+    result |= -((uint64_t) 1 << shift);
+
+  return result;
+}
+
+/* Read a 32-bit IEEE float from PC using INFO, convert it to a host
+   double, and store it at VALUE.  */
+
+static int
+read_f32 (double *value, bfd_vma pc, struct disassemble_info *info)
+{
+  bfd_byte buf[4];
+
+  if (info->read_memory_func (pc, buf, sizeof (buf), info))
+    return -1;
+
+  floatformat_to_double (&floatformat_ieee_single_little, buf,
+                         value);
+
+  return sizeof (buf);
+}
+
+/* Read a 64-bit IEEE float from PC using INFO, convert it to a host
+   double, and store it at VALUE.  */
+
+static int
+read_f64 (double *value, bfd_vma pc, struct disassemble_info *info)
+{
+  bfd_byte buf[8];
+
+  if (info->read_memory_func (pc, buf, sizeof (buf), info))
+    return -1;
+
+  floatformat_to_double (&floatformat_ieee_double_little, buf,
+                         value);
+
+  return sizeof (buf);
+}
+
+/* Main disassembly routine.  Disassemble insn at PC using INFO.  */
+
+int
+print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info)
+{
+  unsigned char opcode;
+  struct wasm32_opcode_s *op;
+  bfd_byte buffer[16];
+  void *stream = info->stream;
+  fprintf_ftype prin = info->fprintf_func;
+  struct wasm32_private_data *private_data = info->private_data;
+  long long constant = 0;
+  double fconstant = 0.0;
+  long flags = 0;
+  long offset = 0;
+  long depth = 0;
+  long index = 0;
+  long target_count = 0;
+  long block_type = 0;
+  int len = 1;
+  int ret = 0;
+  unsigned int bytes_read = 0;
+  int i;
+  const char *locals[] =
+    {
+      "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0",
+      "$rp", "$fp", "$sp",
+      "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
+      "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7",
+      "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
+    };
+  int nlocals = ARRAY_SIZE (locals);
+  const char *globals[] =
+    {
+      "$got", "$plt", "$gpo"
+    };
+  int nglobals = ARRAY_SIZE (globals);
+  bfd_boolean error = FALSE;
+
+  if (info->read_memory_func (pc, buffer, 1, info))
+    return -1;
+
+  opcode = buffer[0];
+
+  for (op = wasm32_opcodes; op->name; op++)
+    if (op->opcode == opcode)
+      break;
+
+  if (!op->name)
+    {
+      prin (stream, "\t.byte 0x%02x\n", buffer[0]);
+      return 1;
+    }
+  else
+    {
+      len = 1;
+
+      prin (stream, "\t");
+      prin (stream, "%s", op->name);
+
+      if (op->clas == wasm_typed)
+        {
+          block_type = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          switch (block_type)
+            {
+            case BLOCK_TYPE_NONE:
+              prin (stream, "[]");
+              break;
+            case BLOCK_TYPE_I32:
+              prin (stream, "[i]");
+              break;
+            case BLOCK_TYPE_I64:
+              prin (stream, "[l]");
+              break;
+            case BLOCK_TYPE_F32:
+              prin (stream, "[f]");
+              break;
+            case BLOCK_TYPE_F64:
+              prin (stream, "[d]");
+              break;
+            }
+        }
+
+      switch (op->clas)
+        {
+        case wasm_special:
+        case wasm_eqz:
+        case wasm_binary:
+        case wasm_unary:
+        case wasm_conv:
+        case wasm_relational:
+        case wasm_drop:
+        case wasm_signature:
+        case wasm_call_import:
+        case wasm_typed:
+        case wasm_select:
+          break;
+
+        case wasm_break_table:
+          target_count = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %ld", target_count);
+          for (i = 0; i < target_count + 1; i++)
+            {
+              long target = 0;
+              target = wasm_read_leb128
+                (pc + len, info, &error, &bytes_read, FALSE);
+              if (error)
+                return -1;
+              len += bytes_read;
+              prin (stream, " %ld", target);
+            }
+          break;
+
+        case wasm_break:
+        case wasm_break_if:
+          depth = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %ld", depth);
+          break;
+
+        case wasm_return:
+          break;
+
+        case wasm_constant_i32:
+        case wasm_constant_i64:
+          constant = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, TRUE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %lld", constant);
+          break;
+
+        case wasm_constant_f32:
+          /* This appears to be the best we can do, even though we're
+             using host doubles for WebAssembly floats.  */
+          ret = read_f32 (&fconstant, pc + len, info);
+          if (ret < 0)
+            return -1;
+          len += ret;
+          prin (stream, " %.*g", DECIMAL_DIG, fconstant);
+          break;
+
+        case wasm_constant_f64:
+          ret = read_f64 (&fconstant, pc + len, info);
+          if (ret < 0)
+            return -1;
+          len += ret;
+          prin (stream, " %.*g", DECIMAL_DIG, fconstant);
+          break;
+
+        case wasm_call:
+          index = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " ");
+          private_data->section_prefix = ".space.function_index";
+          (*info->print_address_func) ((bfd_vma) index, info);
+          private_data->section_prefix = NULL;
+          break;
+
+        case wasm_call_indirect:
+          constant = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %lld", constant);
+          constant = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %lld", constant);
+          break;
+
+        case wasm_get_local:
+        case wasm_set_local:
+        case wasm_tee_local:
+          constant = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %lld", constant);
+          if (strcmp (op->name + 4, "local") == 0)
+            {
+              if (private_data->print_registers
+                  && constant >= 0 && constant < nlocals)
+                prin (stream, " <%s>", locals[constant]);
+            }
+          else
+            {
+              if (private_data->print_well_known_globals
+                  && constant >= 0 && constant < nglobals)
+                prin (stream, " <%s>", globals[constant]);
+            }
+          break;
+
+        case wasm_grow_memory:
+        case wasm_current_memory:
+          constant = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " %lld", constant);
+          break;
+
+        case wasm_load:
+        case wasm_store:
+          flags = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          offset = wasm_read_leb128
+            (pc + len, info, &error, &bytes_read, FALSE);
+          if (error)
+            return -1;
+          len += bytes_read;
+          prin (stream, " a=%ld %ld", flags, offset);
+        }
+    }
+  return len;
+}
+
+/* Print valid disassembler options to STREAM.  */
+
+void
+print_wasm32_disassembler_options (FILE *stream)
+{
+  unsigned int i, max_len = 0;
+
+  fprintf (stream, _("\
+The following WebAssembly-specific disassembler options are supported for use\n\
+with the -M switch:\n"));
+
+  for (i = 0; i < ARRAY_SIZE (options); i++)
+    {
+      unsigned int len = strlen (options[i].name);
+
+      if (max_len < len)
+       max_len = len;
+    }
+
+  for (i = 0, max_len++; i < ARRAY_SIZE (options); i++)
+    fprintf (stream, "  %s%*c %s\n",
+            options[i].name,
+            (int)(max_len - strlen (options[i].name)), ' ',
+            _(options[i].description));
+}