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