Remove path name from test case
[binutils-gdb.git] / opcodes / wasm32-dis.c
1 /* Opcode printing code for the WebAssembly target
2 Copyright (C) 2017-2023 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 }