xref: /netbsd-src/external/gpl3/gdb/dist/opcodes/wasm32-dis.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
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