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