binutils-gdb/opcodes/wasm32-dis.c

528 lines
14 KiB
C
Raw Normal View History

/* Opcode printing code for the WebAssembly target
Copyright (C) 2017-2020 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"
Move print_insn_XXX to an opcodes internal header With the changes done in previous patches, print_insn_XXX functions don't have to be external visible out of opcodes, because both gdb and objdump select disassemblers through a single interface. This patch moves these print_insn_XXX declarations from include/dis-asm.h to opcodes/disassemble.h, which is a new header added by this patch. include: 2017-05-24 Yao Qi <yao.qi@linaro.org> * dis-asm.h: Move some function declarations to opcodes/disassemble.h. opcodes: 2017-05-24 Yao Qi <yao.qi@linaro.org> * alpha-dis.c: Include disassemble.h, don't include dis-asm.h. * avr-dis.c, bfin-dis.c, cr16-dis.c: Likewise. * crx-dis.c, d10v-dis.c, d30v-dis.c: Likewise. * disassemble.c, dlx-dis.c, epiphany-dis.c: Likewise. * fr30-dis.c, ft32-dis.c, h8300-dis.c, h8500-dis.c: Likewise. * hppa-dis.c, i370-dis.c, i386-dis.c: Likewise. * i860-dis.c, i960-dis.c, ip2k-dis.c: Likewise. * iq2000-dis.c, lm32-dis.c, m10200-dis.c: Likewise. * m10300-dis.c, m32r-dis.c, m68hc11-dis.c: Likewise. * m68k-dis.c, m88k-dis.c, mcore-dis.c: Likewise. * metag-dis.c, microblaze-dis.c, mmix-dis.c: Likewise. * moxie-dis.c, msp430-dis.c, mt-dis.c: * nds32-dis.c, nios2-dis.c, ns32k-dis.c: Likewise. * or1k-dis.c, pdp11-dis.c, pj-dis.c: Likewise. * ppc-dis.c, pru-dis.c, riscv-dis.c: Likewise. * rl78-dis.c, s390-dis.c, score-dis.c: Likewise. * sh-dis.c, sh64-dis.c, tic30-dis.c: Likewise. * tic4x-dis.c, tic54x-dis.c, tic6x-dis.c: Likewise. * tic80-dis.c, tilegx-dis.c, tilepro-dis.c: Likewise. * v850-dis.c, vax-dis.c, visium-dis.c: Likewise. * w65-dis.c, wasm32-dis.c, xc16x-dis.c: Likewise. * xgate-dis.c, xstormy16-dis.c, xtensa-dis.c: Likewise. * z80-dis.c, z8k-dis.c: Likewise. * disassemble.h: New file.
2017-05-25 00:23:52 +08:00
#include "disassemble.h"
#include "opintl.h"
#include "safe-ctype.h"
#include "floatformat.h"
#include "libiberty.h"
#include "elf-bfd.h"
#include "elf/internal.h"
#include "elf/wasm32.h"
Include bfd_stdint.h in bfd.h This patch adds bfd_stdint.h to bfd.h, so that BFD can use size_t where appropriate in function parameters and return values. I also tidy a few other cases where headers are included twice. bfd/ * Makefile.am (bfdinclude_HEADERS): Add bfd_stdint.h. (BFD_H_DEPS): Add include/diagnostics.h. (LOCAL_H_DEPS): Add bfd_stdint.h. * bfd-in.h: Include bfd_stdint.h. * arc-plt.h: Don't include stdint.h. * coff-rs6000.c: Likewise. * coff64-rs6000.c: Likewise. * elfxx-riscv.c: Likewise. * cache.c: Don't include bfd_stdint.h. * elf32-arm.c: Likewise. * elf32-avr.c: Likewise. * elf32-nds32.c: Likewise. * elf32-rl78.c: Likewise. * elf32-rx.c: Likewise. * elf32-wasm32.c: Likewise. * elf64-nfp.c: Likewise. * elflink.c: Likewise. * elfnn-aarch64.c: Likewise. * elfnn-ia64.c: Likewise. * elfxx-ia64.c: Likewise. * elfxx-x86.h: Likewise. * wasm-module.c: Likewise, and don't include sysdep.h twice. * elf-nacl.h: Don't include bfd.h. * mach-o.h: Likewise. * elfxx-aarch64.c: Include bfd.h and elf-bfd.h. * elfxx-aarch64.h: Don't include bfd.h, elf-bfd.h or stdint.h. * mach-o-aarch64.c: Include mach-o.h later. * mach-o-arm.c: Likewise. * mach-o-i386.c: Likewise. * mach-o-x86-64.c: Likewise. * mach-o.c: Likewise. * sysdep.h: Don't include ansidecl.h or sys/stat.h. * Makefile.in: Regenerate. * bfd-in2.h: Regenerate. opcodes/ * arm-dis.c: Include bfd.h. * aarch64-opc.c: Include bfd_stdint.h rather than stdint.h. * csky-dis.c: Likewise. * nds32-asm.c: Likewise. * riscv-dis.c: Likewise. * s12z-dis.c: Likewise. * wasm32-dis.c: Likewise.
2018-12-18 16:33:51 +08:00
#include "bfd_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;
int status = 1;
while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
{
num_read++;
if (shift < sizeof (result) * 8)
{
result |= ((uint64_t) (byte & 0x7f)) << shift;
if ((result >> shift) != (byte & 0x7f))
/* Overflow. */
status |= 2;
shift += 7;
}
else if ((byte & 0x7f) != 0)
status |= 2;
if ((byte & 0x80) == 0)
{
status &= ~1;
if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40))
result |= -((uint64_t) 1 << shift);
break;
}
}
if (length_return != NULL)
*length_return = num_read;
if (error_return != NULL)
*error_return = status != 0;
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 function_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, " %.9g", fconstant);
break;
case wasm_constant_f64:
ret = read_f64 (&fconstant, pc + len, info);
if (ret < 0)
return -1;
len += ret;
prin (stream, " %.17g", fconstant);
break;
case wasm_call:
function_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) function_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));
}