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