1 /* Opcode printing code for the WebAssembly target 2 Copyright (C) 2017-2018 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 /* 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 bfd_boolean success = FALSE; 196 197 while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0) 198 { 199 num_read++; 200 201 result |= ((bfd_vma) (byte & 0x7f)) << shift; 202 203 shift += 7; 204 if ((byte & 0x80) == 0) 205 { 206 success = TRUE; 207 break; 208 } 209 } 210 211 if (length_return != NULL) 212 *length_return = num_read; 213 if (error_return != NULL) 214 *error_return = ! success; 215 216 if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40)) 217 result |= -((uint64_t) 1 << shift); 218 219 return result; 220 } 221 222 /* Read a 32-bit IEEE float from PC using INFO, convert it to a host 223 double, and store it at VALUE. */ 224 225 static int 226 read_f32 (double *value, bfd_vma pc, struct disassemble_info *info) 227 { 228 bfd_byte buf[4]; 229 230 if (info->read_memory_func (pc, buf, sizeof (buf), info)) 231 return -1; 232 233 floatformat_to_double (&floatformat_ieee_single_little, buf, 234 value); 235 236 return sizeof (buf); 237 } 238 239 /* Read a 64-bit IEEE float from PC using INFO, convert it to a host 240 double, and store it at VALUE. */ 241 242 static int 243 read_f64 (double *value, bfd_vma pc, struct disassemble_info *info) 244 { 245 bfd_byte buf[8]; 246 247 if (info->read_memory_func (pc, buf, sizeof (buf), info)) 248 return -1; 249 250 floatformat_to_double (&floatformat_ieee_double_little, buf, 251 value); 252 253 return sizeof (buf); 254 } 255 256 /* Main disassembly routine. Disassemble insn at PC using INFO. */ 257 258 int 259 print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info) 260 { 261 unsigned char opcode; 262 struct wasm32_opcode_s *op; 263 bfd_byte buffer[16]; 264 void *stream = info->stream; 265 fprintf_ftype prin = info->fprintf_func; 266 struct wasm32_private_data *private_data = info->private_data; 267 long long constant = 0; 268 double fconstant = 0.0; 269 long flags = 0; 270 long offset = 0; 271 long depth = 0; 272 long function_index = 0; 273 long target_count = 0; 274 long block_type = 0; 275 int len = 1; 276 int ret = 0; 277 unsigned int bytes_read = 0; 278 int i; 279 const char *locals[] = 280 { 281 "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0", 282 "$rp", "$fp", "$sp", 283 "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", 284 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7", 285 "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", 286 }; 287 int nlocals = ARRAY_SIZE (locals); 288 const char *globals[] = 289 { 290 "$got", "$plt", "$gpo" 291 }; 292 int nglobals = ARRAY_SIZE (globals); 293 bfd_boolean error = FALSE; 294 295 if (info->read_memory_func (pc, buffer, 1, info)) 296 return -1; 297 298 opcode = buffer[0]; 299 300 for (op = wasm32_opcodes; op->name; op++) 301 if (op->opcode == opcode) 302 break; 303 304 if (!op->name) 305 { 306 prin (stream, "\t.byte 0x%02x\n", buffer[0]); 307 return 1; 308 } 309 else 310 { 311 len = 1; 312 313 prin (stream, "\t"); 314 prin (stream, "%s", op->name); 315 316 if (op->clas == wasm_typed) 317 { 318 block_type = wasm_read_leb128 319 (pc + len, info, &error, &bytes_read, FALSE); 320 if (error) 321 return -1; 322 len += bytes_read; 323 switch (block_type) 324 { 325 case BLOCK_TYPE_NONE: 326 prin (stream, "[]"); 327 break; 328 case BLOCK_TYPE_I32: 329 prin (stream, "[i]"); 330 break; 331 case BLOCK_TYPE_I64: 332 prin (stream, "[l]"); 333 break; 334 case BLOCK_TYPE_F32: 335 prin (stream, "[f]"); 336 break; 337 case BLOCK_TYPE_F64: 338 prin (stream, "[d]"); 339 break; 340 } 341 } 342 343 switch (op->clas) 344 { 345 case wasm_special: 346 case wasm_eqz: 347 case wasm_binary: 348 case wasm_unary: 349 case wasm_conv: 350 case wasm_relational: 351 case wasm_drop: 352 case wasm_signature: 353 case wasm_call_import: 354 case wasm_typed: 355 case wasm_select: 356 break; 357 358 case wasm_break_table: 359 target_count = wasm_read_leb128 360 (pc + len, info, &error, &bytes_read, FALSE); 361 if (error) 362 return -1; 363 len += bytes_read; 364 prin (stream, " %ld", target_count); 365 for (i = 0; i < target_count + 1; i++) 366 { 367 long target = 0; 368 target = wasm_read_leb128 369 (pc + len, info, &error, &bytes_read, FALSE); 370 if (error) 371 return -1; 372 len += bytes_read; 373 prin (stream, " %ld", target); 374 } 375 break; 376 377 case wasm_break: 378 case wasm_break_if: 379 depth = wasm_read_leb128 380 (pc + len, info, &error, &bytes_read, FALSE); 381 if (error) 382 return -1; 383 len += bytes_read; 384 prin (stream, " %ld", depth); 385 break; 386 387 case wasm_return: 388 break; 389 390 case wasm_constant_i32: 391 case wasm_constant_i64: 392 constant = wasm_read_leb128 393 (pc + len, info, &error, &bytes_read, TRUE); 394 if (error) 395 return -1; 396 len += bytes_read; 397 prin (stream, " %lld", constant); 398 break; 399 400 case wasm_constant_f32: 401 /* This appears to be the best we can do, even though we're 402 using host doubles for WebAssembly floats. */ 403 ret = read_f32 (&fconstant, pc + len, info); 404 if (ret < 0) 405 return -1; 406 len += ret; 407 prin (stream, " %.9g", fconstant); 408 break; 409 410 case wasm_constant_f64: 411 ret = read_f64 (&fconstant, pc + len, info); 412 if (ret < 0) 413 return -1; 414 len += ret; 415 prin (stream, " %.17g", fconstant); 416 break; 417 418 case wasm_call: 419 function_index = wasm_read_leb128 420 (pc + len, info, &error, &bytes_read, FALSE); 421 if (error) 422 return -1; 423 len += bytes_read; 424 prin (stream, " "); 425 private_data->section_prefix = ".space.function_index"; 426 (*info->print_address_func) ((bfd_vma) function_index, info); 427 private_data->section_prefix = NULL; 428 break; 429 430 case wasm_call_indirect: 431 constant = wasm_read_leb128 432 (pc + len, info, &error, &bytes_read, FALSE); 433 if (error) 434 return -1; 435 len += bytes_read; 436 prin (stream, " %lld", constant); 437 constant = wasm_read_leb128 438 (pc + len, info, &error, &bytes_read, FALSE); 439 if (error) 440 return -1; 441 len += bytes_read; 442 prin (stream, " %lld", constant); 443 break; 444 445 case wasm_get_local: 446 case wasm_set_local: 447 case wasm_tee_local: 448 constant = wasm_read_leb128 449 (pc + len, info, &error, &bytes_read, FALSE); 450 if (error) 451 return -1; 452 len += bytes_read; 453 prin (stream, " %lld", constant); 454 if (strcmp (op->name + 4, "local") == 0) 455 { 456 if (private_data->print_registers 457 && constant >= 0 && constant < nlocals) 458 prin (stream, " <%s>", locals[constant]); 459 } 460 else 461 { 462 if (private_data->print_well_known_globals 463 && constant >= 0 && constant < nglobals) 464 prin (stream, " <%s>", globals[constant]); 465 } 466 break; 467 468 case wasm_grow_memory: 469 case wasm_current_memory: 470 constant = wasm_read_leb128 471 (pc + len, info, &error, &bytes_read, FALSE); 472 if (error) 473 return -1; 474 len += bytes_read; 475 prin (stream, " %lld", constant); 476 break; 477 478 case wasm_load: 479 case wasm_store: 480 flags = wasm_read_leb128 481 (pc + len, info, &error, &bytes_read, FALSE); 482 if (error) 483 return -1; 484 len += bytes_read; 485 offset = wasm_read_leb128 486 (pc + len, info, &error, &bytes_read, FALSE); 487 if (error) 488 return -1; 489 len += bytes_read; 490 prin (stream, " a=%ld %ld", flags, offset); 491 } 492 } 493 return len; 494 } 495 496 /* Print valid disassembler options to STREAM. */ 497 498 void 499 print_wasm32_disassembler_options (FILE *stream) 500 { 501 unsigned int i, max_len = 0; 502 503 fprintf (stream, _("\ 504 The following WebAssembly-specific disassembler options are supported for use\n\ 505 with the -M switch:\n")); 506 507 for (i = 0; i < ARRAY_SIZE (options); i++) 508 { 509 unsigned int len = strlen (options[i].name); 510 511 if (max_len < len) 512 max_len = len; 513 } 514 515 for (i = 0, max_len++; i < ARRAY_SIZE (options); i++) 516 fprintf (stream, " %s%*c %s\n", 517 options[i].name, 518 (int)(max_len - strlen (options[i].name)), ' ', 519 _(options[i].description)); 520 } 521