Browse Source
include * dis-asm.h: Add prototypes for wasm32 disassembler. opcodes * Makefile.am: Add wasm32-dis.c. * configure.ac: Add wasm32-dis.c to wasm32 target. * disassemble.c: Add wasm32 disassembler code. * wasm32-dis.c: New file. * Makefile.in: Regenerate. * configure: Regenerate. * po/POTFILES.in: Regenerate. * po/opcodes.pot: Regenerate. gas * testsuite/gas/wasm32/allinsn.d: Adjust test for disassembler changes. * testsuite/gas/wasm32/disass.d: New test. * testsuite/gas/wasm32/disass.s: New test. * testsuite/gas/wasm32/disass-2.d: New test. * testsuite/gas/wasm32/disass-2.s: New test. * testsuite/gas/wasm32/reloc.d: Adjust test for changed reloc names. * testsuite/gas/wasm32/reloc.s: Update test for changed assembler syntax. * testsuite/gas/wasm32/wasm32.exp: Run new tests. Expect allinsn test to succeed.gdb-8.0-branch
committed by
Nick Clifton
20 changed files with 654 additions and 41 deletions
@ -0,0 +1,9 @@ |
|||
#as: |
|||
#objdump: -d -Mglobals |
|||
#name: disass-2.d |
|||
^dump.o: file format elf32-wasm32$ |
|||
|
|||
^Disassembly of section .text:$ |
|||
^00000000 <.text>:$ |
|||
^ 0: 20 00 get_local 0$ |
|||
^ 2: 23 00 get_global 0 <\$got>$ |
|||
@ -0,0 +1,3 @@ |
|||
.text |
|||
get_local 0 |
|||
get_global 0 |
|||
@ -0,0 +1,9 @@ |
|||
#as: |
|||
#objdump: -d -Mregisters,globals |
|||
#name: disass.d |
|||
^dump.o: file format elf32-wasm32$ |
|||
|
|||
^Disassembly of section .text:$ |
|||
^00000000 <.text>:$ |
|||
^ 0: 20 00 get_local 0 <\$dpc>$ |
|||
^ 2: 23 00 get_global 0 <\$got>$ |
|||
@ -0,0 +1,3 @@ |
|||
.text |
|||
get_local 0 |
|||
get_global 0 |
|||
@ -1,3 +1,3 @@ |
|||
i32.const f@plt |
|||
i32.const f@plt{__sigchar_FiiiiiiiE} |
|||
i32.const x@got |
|||
i32.const f@gotcode |
|||
|
|||
@ -0,0 +1,521 @@ |
|||
/* Opcode printing code for the WebAssembly target
|
|||
Copyright (C) 2017 Free Software Foundation, Inc. |
|||
|
|||
This file is part of libopcodes. |
|||
|
|||
This library is free software; you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation; either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
It is distributed in the hope that it will be useful, but WITHOUT |
|||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
|||
License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program; if not, write to the Free Software |
|||
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, |
|||
MA 02110-1301, USA. */ |
|||
|
|||
#include "sysdep.h" |
|||
#include "dis-asm.h" |
|||
#include "opintl.h" |
|||
#include "safe-ctype.h" |
|||
#include "floatformat.h" |
|||
#include <float.h> |
|||
#include "libiberty.h" |
|||
#include "elf-bfd.h" |
|||
#include "elf/internal.h" |
|||
#include "elf/wasm32.h" |
|||
#include <stdint.h> |
|||
|
|||
/* Type names for blocks and signatures. */ |
|||
#define BLOCK_TYPE_NONE 0x40 |
|||
#define BLOCK_TYPE_I32 0x7f |
|||
#define BLOCK_TYPE_I64 0x7e |
|||
#define BLOCK_TYPE_F32 0x7d |
|||
#define BLOCK_TYPE_F64 0x7c |
|||
|
|||
enum wasm_class |
|||
{ |
|||
wasm_typed, |
|||
wasm_special, |
|||
wasm_break, |
|||
wasm_break_if, |
|||
wasm_break_table, |
|||
wasm_return, |
|||
wasm_call, |
|||
wasm_call_import, |
|||
wasm_call_indirect, |
|||
wasm_get_local, |
|||
wasm_set_local, |
|||
wasm_tee_local, |
|||
wasm_drop, |
|||
wasm_constant_i32, |
|||
wasm_constant_i64, |
|||
wasm_constant_f32, |
|||
wasm_constant_f64, |
|||
wasm_unary, |
|||
wasm_binary, |
|||
wasm_conv, |
|||
wasm_load, |
|||
wasm_store, |
|||
wasm_select, |
|||
wasm_relational, |
|||
wasm_eqz, |
|||
wasm_current_memory, |
|||
wasm_grow_memory, |
|||
wasm_signature |
|||
}; |
|||
|
|||
struct wasm32_private_data |
|||
{ |
|||
bfd_boolean print_registers; |
|||
bfd_boolean print_well_known_globals; |
|||
|
|||
/* Limit valid symbols to those with a given prefix. */ |
|||
const char *section_prefix; |
|||
}; |
|||
|
|||
typedef struct |
|||
{ |
|||
const char *name; |
|||
const char *description; |
|||
} wasm32_options_t; |
|||
|
|||
static const wasm32_options_t options[] = |
|||
{ |
|||
{ "registers", N_("Disassemble \"register\" names") }, |
|||
{ "globals", N_("Name well-known globals") }, |
|||
}; |
|||
|
|||
#define WASM_OPCODE(opcode, name, intype, outtype, clas, signedness) \ |
|||
{ name, wasm_ ## clas, opcode }, |
|||
|
|||
struct wasm32_opcode_s |
|||
{ |
|||
const char *name; |
|||
enum wasm_class clas; |
|||
unsigned char opcode; |
|||
} wasm32_opcodes[] = |
|||
{ |
|||
#include "opcode/wasm.h" |
|||
{ NULL, 0, 0 } |
|||
}; |
|||
|
|||
/* Parse the disassembler options in OPTS and initialize INFO. */ |
|||
|
|||
static void |
|||
parse_wasm32_disassembler_options (struct disassemble_info *info, |
|||
const char *opts) |
|||
{ |
|||
struct wasm32_private_data *private = info->private_data; |
|||
|
|||
while (opts != NULL) |
|||
{ |
|||
if (CONST_STRNEQ (opts, "registers")) |
|||
private->print_registers = TRUE; |
|||
else if (CONST_STRNEQ (opts, "globals")) |
|||
private->print_well_known_globals = TRUE; |
|||
|
|||
opts = strchr (opts, ','); |
|||
if (opts) |
|||
opts++; |
|||
} |
|||
} |
|||
|
|||
/* Check whether SYM is valid. Special-case absolute symbols, which
|
|||
are unhelpful to print, and arguments to a "call" insn, which we |
|||
want to be in a section matching a given prefix. */ |
|||
|
|||
static bfd_boolean |
|||
wasm32_symbol_is_valid (asymbol *sym, |
|||
struct disassemble_info *info) |
|||
{ |
|||
struct wasm32_private_data *private_data = info->private_data; |
|||
|
|||
if (sym == NULL) |
|||
return FALSE; |
|||
|
|||
if (strcmp(sym->section->name, "*ABS*") == 0) |
|||
return FALSE; |
|||
|
|||
if (private_data && private_data->section_prefix != NULL |
|||
&& strncmp (sym->section->name, private_data->section_prefix, |
|||
strlen (private_data->section_prefix))) |
|||
return FALSE; |
|||
|
|||
return TRUE; |
|||
} |
|||
|
|||
/* Initialize the disassembler structures for INFO. */ |
|||
|
|||
void |
|||
disassemble_init_wasm32 (struct disassemble_info *info) |
|||
{ |
|||
if (info->private_data == NULL) |
|||
{ |
|||
static struct wasm32_private_data private; |
|||
|
|||
private.print_registers = FALSE; |
|||
private.print_well_known_globals = FALSE; |
|||
private.section_prefix = NULL; |
|||
|
|||
info->private_data = &private; |
|||
} |
|||
|
|||
if (info->disassembler_options) |
|||
{ |
|||
parse_wasm32_disassembler_options (info, info->disassembler_options); |
|||
|
|||
info->disassembler_options = NULL; |
|||
} |
|||
|
|||
info->symbol_is_valid = wasm32_symbol_is_valid; |
|||
} |
|||
|
|||
/* Read an LEB128-encoded integer from INFO at address PC, reading one
|
|||
byte at a time. Set ERROR_RETURN if no complete integer could be |
|||
read, LENGTH_RETURN to the number oof bytes read (including bytes |
|||
in incomplete numbers). SIGN means interpret the number as |
|||
SLEB128. Unfortunately, this is a duplicate of wasm-module.c's |
|||
wasm_read_leb128 (). */ |
|||
|
|||
static uint64_t |
|||
wasm_read_leb128 (bfd_vma pc, |
|||
struct disassemble_info * info, |
|||
bfd_boolean * error_return, |
|||
unsigned int * length_return, |
|||
bfd_boolean sign) |
|||
{ |
|||
uint64_t result = 0; |
|||
unsigned int num_read = 0; |
|||
unsigned int shift = 0; |
|||
unsigned char byte = 0; |
|||
bfd_boolean success = FALSE; |
|||
|
|||
while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0) |
|||
{ |
|||
num_read++; |
|||
|
|||
result |= ((bfd_vma) (byte & 0x7f)) << shift; |
|||
|
|||
shift += 7; |
|||
if ((byte & 0x80) == 0) |
|||
{ |
|||
success = TRUE; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (length_return != NULL) |
|||
*length_return = num_read; |
|||
if (error_return != NULL) |
|||
*error_return = ! success; |
|||
|
|||
if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40)) |
|||
result |= -((uint64_t) 1 << shift); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/* Read a 32-bit IEEE float from PC using INFO, convert it to a host
|
|||
double, and store it at VALUE. */ |
|||
|
|||
static int |
|||
read_f32 (double *value, bfd_vma pc, struct disassemble_info *info) |
|||
{ |
|||
bfd_byte buf[4]; |
|||
|
|||
if (info->read_memory_func (pc, buf, sizeof (buf), info)) |
|||
return -1; |
|||
|
|||
floatformat_to_double (&floatformat_ieee_single_little, buf, |
|||
value); |
|||
|
|||
return sizeof (buf); |
|||
} |
|||
|
|||
/* Read a 64-bit IEEE float from PC using INFO, convert it to a host
|
|||
double, and store it at VALUE. */ |
|||
|
|||
static int |
|||
read_f64 (double *value, bfd_vma pc, struct disassemble_info *info) |
|||
{ |
|||
bfd_byte buf[8]; |
|||
|
|||
if (info->read_memory_func (pc, buf, sizeof (buf), info)) |
|||
return -1; |
|||
|
|||
floatformat_to_double (&floatformat_ieee_double_little, buf, |
|||
value); |
|||
|
|||
return sizeof (buf); |
|||
} |
|||
|
|||
/* Main disassembly routine. Disassemble insn at PC using INFO. */ |
|||
|
|||
int |
|||
print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info) |
|||
{ |
|||
unsigned char opcode; |
|||
struct wasm32_opcode_s *op; |
|||
bfd_byte buffer[16]; |
|||
void *stream = info->stream; |
|||
fprintf_ftype prin = info->fprintf_func; |
|||
struct wasm32_private_data *private_data = info->private_data; |
|||
long long constant = 0; |
|||
double fconstant = 0.0; |
|||
long flags = 0; |
|||
long offset = 0; |
|||
long depth = 0; |
|||
long index = 0; |
|||
long target_count = 0; |
|||
long block_type = 0; |
|||
int len = 1; |
|||
int ret = 0; |
|||
unsigned int bytes_read = 0; |
|||
int i; |
|||
const char *locals[] = |
|||
{ |
|||
"$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0", |
|||
"$rp", "$fp", "$sp", |
|||
"$r2", "$r3", "$r4", "$r5", "$r6", "$r7", |
|||
"$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7", |
|||
"$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
|||
}; |
|||
int nlocals = ARRAY_SIZE (locals); |
|||
const char *globals[] = |
|||
{ |
|||
"$got", "$plt", "$gpo" |
|||
}; |
|||
int nglobals = ARRAY_SIZE (globals); |
|||
bfd_boolean error = FALSE; |
|||
|
|||
if (info->read_memory_func (pc, buffer, 1, info)) |
|||
return -1; |
|||
|
|||
opcode = buffer[0]; |
|||
|
|||
for (op = wasm32_opcodes; op->name; op++) |
|||
if (op->opcode == opcode) |
|||
break; |
|||
|
|||
if (!op->name) |
|||
{ |
|||
prin (stream, "\t.byte 0x%02x\n", buffer[0]); |
|||
return 1; |
|||
} |
|||
else |
|||
{ |
|||
len = 1; |
|||
|
|||
prin (stream, "\t"); |
|||
prin (stream, "%s", op->name); |
|||
|
|||
if (op->clas == wasm_typed) |
|||
{ |
|||
block_type = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
switch (block_type) |
|||
{ |
|||
case BLOCK_TYPE_NONE: |
|||
prin (stream, "[]"); |
|||
break; |
|||
case BLOCK_TYPE_I32: |
|||
prin (stream, "[i]"); |
|||
break; |
|||
case BLOCK_TYPE_I64: |
|||
prin (stream, "[l]"); |
|||
break; |
|||
case BLOCK_TYPE_F32: |
|||
prin (stream, "[f]"); |
|||
break; |
|||
case BLOCK_TYPE_F64: |
|||
prin (stream, "[d]"); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
switch (op->clas) |
|||
{ |
|||
case wasm_special: |
|||
case wasm_eqz: |
|||
case wasm_binary: |
|||
case wasm_unary: |
|||
case wasm_conv: |
|||
case wasm_relational: |
|||
case wasm_drop: |
|||
case wasm_signature: |
|||
case wasm_call_import: |
|||
case wasm_typed: |
|||
case wasm_select: |
|||
break; |
|||
|
|||
case wasm_break_table: |
|||
target_count = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %ld", target_count); |
|||
for (i = 0; i < target_count + 1; i++) |
|||
{ |
|||
long target = 0; |
|||
target = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %ld", target); |
|||
} |
|||
break; |
|||
|
|||
case wasm_break: |
|||
case wasm_break_if: |
|||
depth = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %ld", depth); |
|||
break; |
|||
|
|||
case wasm_return: |
|||
break; |
|||
|
|||
case wasm_constant_i32: |
|||
case wasm_constant_i64: |
|||
constant = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, TRUE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %lld", constant); |
|||
break; |
|||
|
|||
case wasm_constant_f32: |
|||
/* This appears to be the best we can do, even though we're
|
|||
using host doubles for WebAssembly floats. */ |
|||
ret = read_f32 (&fconstant, pc + len, info); |
|||
if (ret < 0) |
|||
return -1; |
|||
len += ret; |
|||
prin (stream, " %.*g", DECIMAL_DIG, fconstant); |
|||
break; |
|||
|
|||
case wasm_constant_f64: |
|||
ret = read_f64 (&fconstant, pc + len, info); |
|||
if (ret < 0) |
|||
return -1; |
|||
len += ret; |
|||
prin (stream, " %.*g", DECIMAL_DIG, fconstant); |
|||
break; |
|||
|
|||
case wasm_call: |
|||
index = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " "); |
|||
private_data->section_prefix = ".space.function_index"; |
|||
(*info->print_address_func) ((bfd_vma) index, info); |
|||
private_data->section_prefix = NULL; |
|||
break; |
|||
|
|||
case wasm_call_indirect: |
|||
constant = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %lld", constant); |
|||
constant = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %lld", constant); |
|||
break; |
|||
|
|||
case wasm_get_local: |
|||
case wasm_set_local: |
|||
case wasm_tee_local: |
|||
constant = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %lld", constant); |
|||
if (strcmp (op->name + 4, "local") == 0) |
|||
{ |
|||
if (private_data->print_registers |
|||
&& constant >= 0 && constant < nlocals) |
|||
prin (stream, " <%s>", locals[constant]); |
|||
} |
|||
else |
|||
{ |
|||
if (private_data->print_well_known_globals |
|||
&& constant >= 0 && constant < nglobals) |
|||
prin (stream, " <%s>", globals[constant]); |
|||
} |
|||
break; |
|||
|
|||
case wasm_grow_memory: |
|||
case wasm_current_memory: |
|||
constant = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " %lld", constant); |
|||
break; |
|||
|
|||
case wasm_load: |
|||
case wasm_store: |
|||
flags = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
offset = wasm_read_leb128 |
|||
(pc + len, info, &error, &bytes_read, FALSE); |
|||
if (error) |
|||
return -1; |
|||
len += bytes_read; |
|||
prin (stream, " a=%ld %ld", flags, offset); |
|||
} |
|||
} |
|||
return len; |
|||
} |
|||
|
|||
/* Print valid disassembler options to STREAM. */ |
|||
|
|||
void |
|||
print_wasm32_disassembler_options (FILE *stream) |
|||
{ |
|||
unsigned int i, max_len = 0; |
|||
|
|||
fprintf (stream, _("\
|
|||
The following WebAssembly-specific disassembler options are supported for use\n\ |
|||
with the -M switch:\n")); |
|||
|
|||
for (i = 0; i < ARRAY_SIZE (options); i++) |
|||
{ |
|||
unsigned int len = strlen (options[i].name); |
|||
|
|||
if (max_len < len) |
|||
max_len = len; |
|||
} |
|||
|
|||
for (i = 0, max_len++; i < ARRAY_SIZE (options); i++) |
|||
fprintf (stream, " %s%*c %s\n", |
|||
options[i].name, |
|||
(int)(max_len - strlen (options[i].name)), ' ', |
|||
_(options[i].description)); |
|||
} |
|||
Loading…
Reference in new issue