/src/binutils-gdb/opcodes/wasm32-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Opcode printing code for the WebAssembly target |
2 | | Copyright (C) 2017-2025 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 | 146 | #define BLOCK_TYPE_NONE 0x40 |
39 | 99 | #define BLOCK_TYPE_I32 0x7f |
40 | 37 | #define BLOCK_TYPE_I64 0x7e |
41 | 71 | #define BLOCK_TYPE_F32 0x7d |
42 | 143 | #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 | 0 | { |
117 | 0 | struct wasm32_private_data *private = info->private_data; |
118 | |
|
119 | 0 | while (opts != NULL) |
120 | 0 | { |
121 | 0 | if (startswith (opts, "registers")) |
122 | 0 | private->print_registers = true; |
123 | 0 | else if (startswith (opts, "globals")) |
124 | 0 | private->print_well_known_globals = true; |
125 | |
|
126 | 0 | opts = strchr (opts, ','); |
127 | 0 | if (opts) |
128 | 0 | opts++; |
129 | 0 | } |
130 | 0 | } |
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 | 0 | { |
140 | 0 | struct wasm32_private_data *private_data = info->private_data; |
141 | |
|
142 | 0 | if (sym == NULL) |
143 | 0 | return false; |
144 | | |
145 | 0 | if (strcmp(sym->section->name, "*ABS*") == 0) |
146 | 0 | return false; |
147 | | |
148 | 0 | if (private_data && private_data->section_prefix != NULL |
149 | 0 | && strncmp (sym->section->name, private_data->section_prefix, |
150 | 0 | strlen (private_data->section_prefix))) |
151 | 0 | return false; |
152 | | |
153 | 0 | return true; |
154 | 0 | } |
155 | | |
156 | | /* Initialize the disassembler structures for INFO. */ |
157 | | |
158 | | void |
159 | | disassemble_init_wasm32 (struct disassemble_info *info) |
160 | 753 | { |
161 | 753 | if (info->private_data == NULL) |
162 | 753 | { |
163 | 753 | static struct wasm32_private_data private; |
164 | | |
165 | 753 | private.print_registers = false; |
166 | 753 | private.print_well_known_globals = false; |
167 | 753 | private.section_prefix = NULL; |
168 | | |
169 | 753 | info->private_data = &private; |
170 | 753 | } |
171 | | |
172 | 753 | if (info->disassembler_options) |
173 | 0 | { |
174 | 0 | parse_wasm32_disassembler_options (info, info->disassembler_options); |
175 | |
|
176 | 0 | info->disassembler_options = NULL; |
177 | 0 | } |
178 | | |
179 | 753 | info->symbol_is_valid = wasm32_symbol_is_valid; |
180 | 753 | } |
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 | 19.4k | { |
196 | 19.4k | uint64_t result = 0; |
197 | 19.4k | unsigned int num_read = 0; |
198 | 19.4k | unsigned int shift = 0; |
199 | 19.4k | unsigned char byte = 0; |
200 | 19.4k | unsigned char lost, mask; |
201 | 19.4k | int status = 1; |
202 | | |
203 | 31.4k | while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0) |
204 | 31.4k | { |
205 | 31.4k | num_read++; |
206 | | |
207 | 31.4k | if (shift < CHAR_BIT * sizeof (result)) |
208 | 28.6k | { |
209 | 28.6k | result |= ((uint64_t) (byte & 0x7f)) << shift; |
210 | | /* These bits overflowed. */ |
211 | 28.6k | lost = byte ^ (result >> shift); |
212 | | /* And this is the mask of possible overflow bits. */ |
213 | 28.6k | mask = 0x7f ^ ((uint64_t) 0x7f << shift >> shift); |
214 | 28.6k | shift += 7; |
215 | 28.6k | } |
216 | 2.78k | else |
217 | 2.78k | { |
218 | 2.78k | lost = byte; |
219 | 2.78k | mask = 0x7f; |
220 | 2.78k | } |
221 | 31.4k | if ((lost & mask) != (sign && (int64_t) result < 0 ? mask : 0)) |
222 | 2.16k | status |= 2; |
223 | | |
224 | 31.4k | if ((byte & 0x80) == 0) |
225 | 19.4k | { |
226 | 19.4k | status &= ~1; |
227 | 19.4k | if (sign && shift < CHAR_BIT * sizeof (result) && (byte & 0x40)) |
228 | 1.38k | result |= -((uint64_t) 1 << shift); |
229 | 19.4k | break; |
230 | 19.4k | } |
231 | 31.4k | } |
232 | | |
233 | 19.4k | if (length_return != NULL) |
234 | 19.4k | *length_return = num_read; |
235 | 19.4k | if (error_return != NULL) |
236 | 19.4k | *error_return = status != 0; |
237 | | |
238 | 19.4k | return result; |
239 | 19.4k | } |
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 | 468 | { |
247 | 468 | bfd_byte buf[4]; |
248 | | |
249 | 468 | if (info->read_memory_func (pc, buf, sizeof (buf), info)) |
250 | 2 | return -1; |
251 | | |
252 | 466 | floatformat_to_double (&floatformat_ieee_single_little, buf, |
253 | 466 | value); |
254 | | |
255 | 466 | return sizeof (buf); |
256 | 468 | } |
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 | 1.21k | { |
264 | 1.21k | bfd_byte buf[8]; |
265 | | |
266 | 1.21k | if (info->read_memory_func (pc, buf, sizeof (buf), info)) |
267 | 12 | return -1; |
268 | | |
269 | 1.20k | floatformat_to_double (&floatformat_ieee_double_little, buf, |
270 | 1.20k | value); |
271 | | |
272 | 1.20k | return sizeof (buf); |
273 | 1.21k | } |
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 | 84.8k | { |
280 | 84.8k | unsigned char opcode; |
281 | 84.8k | struct wasm32_opcode_s *op; |
282 | 84.8k | bfd_byte buffer[16]; |
283 | 84.8k | void *stream = info->stream; |
284 | 84.8k | fprintf_ftype prin = info->fprintf_func; |
285 | 84.8k | struct wasm32_private_data *private_data = info->private_data; |
286 | 84.8k | uint64_t val; |
287 | 84.8k | int len; |
288 | 84.8k | unsigned int bytes_read; |
289 | 84.8k | bool error; |
290 | | |
291 | 84.8k | if (info->read_memory_func (pc, buffer, 1, info)) |
292 | 1 | return -1; |
293 | | |
294 | 84.8k | opcode = buffer[0]; |
295 | | |
296 | 6.12M | for (op = wasm32_opcodes; op->name; op++) |
297 | 6.10M | if (op->opcode == opcode) |
298 | 63.2k | break; |
299 | | |
300 | 84.8k | if (!op->name) |
301 | 21.6k | { |
302 | 21.6k | prin (stream, "\t.byte 0x%02x\n", buffer[0]); |
303 | 21.6k | return 1; |
304 | 21.6k | } |
305 | | |
306 | 63.2k | len = 1; |
307 | | |
308 | 63.2k | prin (stream, "\t"); |
309 | 63.2k | prin (stream, "%s", op->name); |
310 | | |
311 | 63.2k | if (op->clas == wasm_typed) |
312 | 530 | { |
313 | 530 | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, false); |
314 | 530 | if (error) |
315 | 17 | return -1; |
316 | 513 | len += bytes_read; |
317 | 513 | switch (val) |
318 | 513 | { |
319 | 146 | case BLOCK_TYPE_NONE: |
320 | 146 | prin (stream, "[]"); |
321 | 146 | break; |
322 | 99 | case BLOCK_TYPE_I32: |
323 | 99 | prin (stream, "[i]"); |
324 | 99 | break; |
325 | 37 | case BLOCK_TYPE_I64: |
326 | 37 | prin (stream, "[l]"); |
327 | 37 | break; |
328 | 71 | case BLOCK_TYPE_F32: |
329 | 71 | prin (stream, "[f]"); |
330 | 71 | break; |
331 | 143 | case BLOCK_TYPE_F64: |
332 | 143 | prin (stream, "[d]"); |
333 | 143 | break; |
334 | 17 | default: |
335 | 17 | return -1; |
336 | 513 | } |
337 | 513 | } |
338 | | |
339 | 63.2k | switch (op->clas) |
340 | 63.2k | { |
341 | 29.7k | case wasm_special: |
342 | 30.1k | case wasm_eqz: |
343 | 36.1k | case wasm_binary: |
344 | 38.7k | case wasm_unary: |
345 | 41.6k | case wasm_conv: |
346 | 48.8k | case wasm_relational: |
347 | 48.9k | case wasm_drop: |
348 | 48.9k | case wasm_signature: |
349 | 48.9k | case wasm_call_import: |
350 | 49.4k | case wasm_typed: |
351 | 49.5k | case wasm_select: |
352 | 49.5k | break; |
353 | | |
354 | 202 | case wasm_break_table: |
355 | 202 | { |
356 | 202 | uint32_t target_count, i; |
357 | 202 | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
358 | 202 | false); |
359 | 202 | target_count = val; |
360 | 202 | if (error || target_count != val || target_count == (uint32_t) -1) |
361 | 30 | return -1; |
362 | 172 | len += bytes_read; |
363 | 172 | prin (stream, " %u", target_count); |
364 | 2.58k | for (i = 0; i < target_count + 1; i++) |
365 | 2.48k | { |
366 | 2.48k | uint32_t target; |
367 | 2.48k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
368 | 2.48k | false); |
369 | 2.48k | target = val; |
370 | 2.48k | if (error || target != val) |
371 | 78 | return -1; |
372 | 2.40k | len += bytes_read; |
373 | 2.40k | prin (stream, " %u", target); |
374 | 2.40k | } |
375 | 172 | } |
376 | 94 | break; |
377 | | |
378 | 593 | case wasm_break: |
379 | 1.22k | case wasm_break_if: |
380 | 1.22k | { |
381 | 1.22k | uint32_t depth; |
382 | 1.22k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
383 | 1.22k | false); |
384 | 1.22k | depth = val; |
385 | 1.22k | if (error || depth != val) |
386 | 57 | return -1; |
387 | 1.16k | len += bytes_read; |
388 | 1.16k | prin (stream, " %u", depth); |
389 | 1.16k | } |
390 | 0 | break; |
391 | | |
392 | 431 | case wasm_return: |
393 | 431 | break; |
394 | | |
395 | 1.26k | case wasm_constant_i32: |
396 | 1.60k | case wasm_constant_i64: |
397 | 1.60k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, true); |
398 | 1.60k | if (error) |
399 | 40 | return -1; |
400 | 1.56k | len += bytes_read; |
401 | 1.56k | prin (stream, " %" PRId64, val); |
402 | 1.56k | break; |
403 | | |
404 | 468 | case wasm_constant_f32: |
405 | 468 | { |
406 | 468 | double fconstant; |
407 | 468 | int ret; |
408 | | /* This appears to be the best we can do, even though we're |
409 | | using host doubles for WebAssembly floats. */ |
410 | 468 | ret = read_f32 (&fconstant, pc + len, info); |
411 | 468 | if (ret < 0) |
412 | 2 | return -1; |
413 | 466 | len += ret; |
414 | 466 | prin (stream, " %.9g", fconstant); |
415 | 466 | } |
416 | 0 | break; |
417 | | |
418 | 1.21k | case wasm_constant_f64: |
419 | 1.21k | { |
420 | 1.21k | double fconstant; |
421 | 1.21k | int ret; |
422 | 1.21k | ret = read_f64 (&fconstant, pc + len, info); |
423 | 1.21k | if (ret < 0) |
424 | 12 | return -1; |
425 | 1.20k | len += ret; |
426 | 1.20k | prin (stream, " %.17g", fconstant); |
427 | 1.20k | } |
428 | 0 | break; |
429 | | |
430 | 630 | case wasm_call: |
431 | 630 | { |
432 | 630 | uint32_t function_index; |
433 | 630 | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
434 | 630 | false); |
435 | 630 | function_index = val; |
436 | 630 | if (error || function_index != val) |
437 | 40 | return -1; |
438 | 590 | len += bytes_read; |
439 | 590 | prin (stream, " "); |
440 | 590 | private_data->section_prefix = ".space.function_index"; |
441 | 590 | (*info->print_address_func) ((bfd_vma) function_index, info); |
442 | 590 | private_data->section_prefix = NULL; |
443 | 590 | } |
444 | 0 | break; |
445 | | |
446 | 990 | case wasm_call_indirect: |
447 | 990 | { |
448 | 990 | uint32_t type_index, xtra_index; |
449 | 990 | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
450 | 990 | false); |
451 | 990 | type_index = val; |
452 | 990 | if (error || type_index != val) |
453 | 34 | return -1; |
454 | 956 | len += bytes_read; |
455 | 956 | prin (stream, " %u", type_index); |
456 | 956 | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
457 | 956 | false); |
458 | 956 | xtra_index = val; |
459 | 956 | if (error || xtra_index != val) |
460 | 54 | return -1; |
461 | 902 | len += bytes_read; |
462 | 902 | prin (stream, " %u", xtra_index); |
463 | 902 | } |
464 | 0 | break; |
465 | | |
466 | 588 | case wasm_get_local: |
467 | 1.26k | case wasm_set_local: |
468 | 1.52k | case wasm_tee_local: |
469 | 1.52k | { |
470 | 1.52k | uint32_t local_index; |
471 | 1.52k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
472 | 1.52k | false); |
473 | 1.52k | local_index = val; |
474 | 1.52k | if (error || local_index != val) |
475 | 49 | return -1; |
476 | 1.47k | len += bytes_read; |
477 | 1.47k | prin (stream, " %u", local_index); |
478 | 1.47k | if (strcmp (op->name + 4, "local") == 0) |
479 | 772 | { |
480 | 772 | static const char *locals[] = |
481 | 772 | { |
482 | 772 | "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0", |
483 | 772 | "$rp", "$fp", "$sp", |
484 | 772 | "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", |
485 | 772 | "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7", |
486 | 772 | "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
487 | 772 | }; |
488 | 772 | if (private_data->print_registers |
489 | 772 | && local_index < ARRAY_SIZE (locals)) |
490 | 0 | prin (stream, " <%s>", locals[local_index]); |
491 | 772 | } |
492 | 705 | else |
493 | 705 | { |
494 | 705 | static const char *globals[] = |
495 | 705 | { |
496 | 705 | "$got", "$plt", "$gpo" |
497 | 705 | }; |
498 | 705 | if (private_data->print_well_known_globals |
499 | 705 | && local_index < ARRAY_SIZE (globals)) |
500 | 0 | prin (stream, " <%s>", globals[local_index]); |
501 | 705 | } |
502 | 1.47k | } |
503 | 0 | break; |
504 | | |
505 | 1.07k | case wasm_grow_memory: |
506 | 1.33k | case wasm_current_memory: |
507 | 1.33k | { |
508 | 1.33k | uint32_t reserved_size; |
509 | 1.33k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
510 | 1.33k | false); |
511 | 1.33k | reserved_size = val; |
512 | 1.33k | if (error || reserved_size != val) |
513 | 50 | return -1; |
514 | 1.28k | len += bytes_read; |
515 | 1.28k | prin (stream, " %u", reserved_size); |
516 | 1.28k | } |
517 | 0 | break; |
518 | | |
519 | 2.46k | case wasm_load: |
520 | 4.03k | case wasm_store: |
521 | 4.03k | { |
522 | 4.03k | uint32_t flags, offset; |
523 | 4.03k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
524 | 4.03k | false); |
525 | 4.03k | flags = val; |
526 | 4.03k | if (error || flags != val) |
527 | 67 | return -1; |
528 | 3.96k | len += bytes_read; |
529 | 3.96k | val = wasm_read_leb128 (pc + len, info, &error, &bytes_read, |
530 | 3.96k | false); |
531 | 3.96k | offset = val; |
532 | 3.96k | if (error || offset != val) |
533 | 68 | return -1; |
534 | 3.89k | len += bytes_read; |
535 | 3.89k | prin (stream, " a=%u %u", flags, offset); |
536 | 3.89k | } |
537 | 0 | break; |
538 | 63.2k | } |
539 | 62.6k | return len; |
540 | 63.2k | } |
541 | | |
542 | | /* Print valid disassembler options to STREAM. */ |
543 | | |
544 | | void |
545 | | print_wasm32_disassembler_options (FILE *stream) |
546 | 0 | { |
547 | 0 | unsigned int i, max_len = 0; |
548 | |
|
549 | 0 | fprintf (stream, _("\ |
550 | 0 | The following WebAssembly-specific disassembler options are supported for use\n\ |
551 | 0 | with the -M switch:\n")); |
552 | |
|
553 | 0 | for (i = 0; i < ARRAY_SIZE (options); i++) |
554 | 0 | { |
555 | 0 | unsigned int len = strlen (options[i].name); |
556 | |
|
557 | 0 | if (max_len < len) |
558 | 0 | max_len = len; |
559 | 0 | } |
560 | |
|
561 | 0 | for (i = 0, max_len++; i < ARRAY_SIZE (options); i++) |
562 | 0 | fprintf (stream, " %s%*c %s\n", |
563 | 0 | options[i].name, |
564 | 0 | (int)(max_len - strlen (options[i].name)), ' ', |
565 | 0 | _(options[i].description)); |
566 | 0 | } |