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