Release 2.33.1
[external/binutils.git] / opcodes / wasm32-dis.c
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 }