gdb: fix use of fprintf_filtered in top.c
[binutils-gdb.git] / opcodes / riscv-dis.c
index fe8dfb88d903a2aa494763b8ca504344d9818b61..57b798d8e14f6a80fd94e0ab985ae76b75e1c8be 100644 (file)
@@ -1,5 +1,5 @@
 /* RISC-V disassembler
-   Copyright (C) 2011-2021 Free Software Foundation, Inc.
+   Copyright (C) 2011-2022 Free Software Foundation, Inc.
 
    Contributed by Andrew Waterman (andrew@sifive.com).
    Based on MIPS target.
 #include "opintl.h"
 #include "elf-bfd.h"
 #include "elf/riscv.h"
-#include "cpu-riscv.h"
+#include "elfxx-riscv.h"
 
 #include <stdint.h>
 #include <ctype.h>
 
+static enum riscv_spec_class default_isa_spec = ISA_SPEC_CLASS_DRAFT - 1;
 static enum riscv_spec_class default_priv_spec = PRIV_SPEC_CLASS_NONE;
 
+unsigned xlen = 0;
+
+static riscv_subset_list_t riscv_subsets;
+static riscv_parse_subset_t riscv_rps_dis =
+{
+  &riscv_subsets,      /* subset_list.  */
+  opcodes_error_handler,/* error_handler.  */
+  &xlen,               /* xlen.  */
+  &default_isa_spec,   /* isa_spec.  */
+  false,               /* check_unknown_prefixed_ext.  */
+};
+
 struct riscv_private_data
 {
   bfd_vma gp;
@@ -41,6 +54,11 @@ struct riscv_private_data
   bfd_vma hi_addr[OP_MASK_RD + 1];
 };
 
+/* Used for mapping symbols.  */
+static int last_map_symbol = -1;
+static bfd_vma last_stop_offset = 0;
+enum riscv_seg_mstate last_map_state;
+
 static const char * const *riscv_gpr_names;
 static const char * const *riscv_fpr_names;
 
@@ -151,7 +169,8 @@ arg_print (struct disassemble_info *info, unsigned long val,
 }
 
 static void
-maybe_print_address (struct riscv_private_data *pd, int base_reg, int offset)
+maybe_print_address (struct riscv_private_data *pd, int base_reg, int offset,
+                    int wide)
 {
   if (pd->hi_addr[base_reg] != (bfd_vma)-1)
     {
@@ -162,27 +181,33 @@ maybe_print_address (struct riscv_private_data *pd, int base_reg, int offset)
     pd->print_addr = pd->gp + offset;
   else if (base_reg == X_TP || base_reg == 0)
     pd->print_addr = offset;
+
+  /* Sign-extend a 32-bit value to a 64-bit value.  */
+  if (wide)
+    pd->print_addr = (bfd_vma)(int32_t) pd->print_addr;
 }
 
 /* Print insn arguments for 32/64-bit code.  */
 
 static void
-print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
+print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info)
 {
   struct riscv_private_data *pd = info->private_data;
   int rs1 = (l >> OP_SH_RS1) & OP_MASK_RS1;
   int rd = (l >> OP_SH_RD) & OP_MASK_RD;
   fprintf_ftype print = info->fprintf_func;
+  const char *opargStart;
 
-  if (*d != '\0')
+  if (*oparg != '\0')
     print (info->stream, "\t");
 
-  for (; *d != '\0'; d++)
+  for (; *oparg != '\0'; oparg++)
     {
-      switch (*d)
+      opargStart = oparg;
+      switch (*oparg)
        {
        case 'C': /* RVC */
-         switch (*++d)
+         switch (*++oparg)
            {
            case 's': /* RS1 x8-x15.  */
            case 'w': /* RS1 x8-x15.  */
@@ -206,6 +231,11 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
              break;
            case 'o':
            case 'j':
+             if (((l & MASK_C_ADDI) == MATCH_C_ADDI) && rd != 0)
+               maybe_print_address (pd, rd, EXTRACT_CITYPE_IMM (l), 0);
+             if (info->mach == bfd_mach_riscv64
+                 && ((l & MASK_C_ADDIW) == MATCH_C_ADDIW) && rd != 0)
+               maybe_print_address (pd, rd, EXTRACT_CITYPE_IMM (l), 1);
              print (info->stream, "%d", (int)EXTRACT_CITYPE_IMM (l));
              break;
            case 'k':
@@ -261,24 +291,93 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
            }
          break;
 
+       case 'V': /* RVV */
+         switch (*++oparg)
+           {
+           case 'd':
+           case 'f':
+             print (info->stream, "%s",
+                    riscv_vecr_names_numeric[EXTRACT_OPERAND (VD, l)]);
+             break;
+           case 'e':
+             if (!EXTRACT_OPERAND (VWD, l))
+               print (info->stream, "%s", riscv_gpr_names[0]);
+             else
+               print (info->stream, "%s",
+                      riscv_vecr_names_numeric[EXTRACT_OPERAND (VD, l)]);
+             break;
+           case 's':
+             print (info->stream, "%s",
+                    riscv_vecr_names_numeric[EXTRACT_OPERAND (VS1, l)]);
+             break;
+           case 't':
+           case 'u': /* VS1 == VS2 already verified at this point.  */
+           case 'v': /* VD == VS1 == VS2 already verified at this point.  */
+             print (info->stream, "%s",
+                    riscv_vecr_names_numeric[EXTRACT_OPERAND (VS2, l)]);
+             break;
+           case '0':
+             print (info->stream, "%s", riscv_vecr_names_numeric[0]);
+             break;
+           case 'b':
+           case 'c':
+             {
+               int imm = (*oparg == 'b') ? EXTRACT_RVV_VB_IMM (l)
+                                         : EXTRACT_RVV_VC_IMM (l);
+               unsigned int imm_vlmul = EXTRACT_OPERAND (VLMUL, imm);
+               unsigned int imm_vsew = EXTRACT_OPERAND (VSEW, imm);
+               unsigned int imm_vta = EXTRACT_OPERAND (VTA, imm);
+               unsigned int imm_vma = EXTRACT_OPERAND (VMA, imm);
+               unsigned int imm_vtype_res = (imm >> 8);
+
+               if (imm_vsew < ARRAY_SIZE (riscv_vsew)
+                   && imm_vlmul < ARRAY_SIZE (riscv_vlmul)
+                   && imm_vta < ARRAY_SIZE (riscv_vta)
+                   && imm_vma < ARRAY_SIZE (riscv_vma)
+                   && !imm_vtype_res
+                   && riscv_vsew[imm_vsew] != NULL
+                   && riscv_vlmul[imm_vlmul] != NULL)
+                 print (info->stream, "%s,%s,%s,%s", riscv_vsew[imm_vsew],
+                        riscv_vlmul[imm_vlmul], riscv_vta[imm_vta],
+                        riscv_vma[imm_vma]);
+               else
+                 print (info->stream, "%d", imm);
+             }
+             break;
+           case 'i':
+             print (info->stream, "%d", (int)EXTRACT_RVV_VI_IMM (l));
+             break;
+           case 'j':
+             print (info->stream, "%d", (int)EXTRACT_RVV_VI_UIMM (l));
+             break;
+           case 'k':
+             print (info->stream, "%d", (int)EXTRACT_RVV_OFFSET (l));
+             break;
+           case 'm':
+             if (! EXTRACT_OPERAND (VMASK, l))
+               print (info->stream, ",%s", riscv_vecm_names_numeric[0]);
+             break;
+           }
+         break;
+
        case ',':
        case '(':
        case ')':
        case '[':
        case ']':
-         print (info->stream, "%c", *d);
+         print (info->stream, "%c", *oparg);
          break;
 
        case '0':
          /* Only print constant 0 if it is the last argument.  */
-         if (!d[1])
+         if (!oparg[1])
            print (info->stream, "0");
          break;
 
        case 'b':
        case 's':
          if ((l & MASK_JALR) == MATCH_JALR)
-           maybe_print_address (pd, rs1, 0);
+           maybe_print_address (pd, rs1, 0, 0);
          print (info->stream, "%s", riscv_gpr_names[rs1]);
          break;
 
@@ -308,17 +407,24 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
          break;
 
        case 'o':
-         maybe_print_address (pd, rs1, EXTRACT_ITYPE_IMM (l));
+         maybe_print_address (pd, rs1, EXTRACT_ITYPE_IMM (l), 0);
          /* Fall through.  */
        case 'j':
          if (((l & MASK_ADDI) == MATCH_ADDI && rs1 != 0)
              || (l & MASK_JALR) == MATCH_JALR)
-           maybe_print_address (pd, rs1, EXTRACT_ITYPE_IMM (l));
+           maybe_print_address (pd, rs1, EXTRACT_ITYPE_IMM (l), 0);
+         if (info->mach == bfd_mach_riscv64
+             && ((l & MASK_ADDIW) == MATCH_ADDIW) && rs1 != 0)
+           maybe_print_address (pd, rs1, EXTRACT_ITYPE_IMM (l), 1);
          print (info->stream, "%d", (int)EXTRACT_ITYPE_IMM (l));
          break;
 
        case 'q':
-         maybe_print_address (pd, rs1, EXTRACT_STYPE_IMM (l));
+         maybe_print_address (pd, rs1, EXTRACT_STYPE_IMM (l), 0);
+         print (info->stream, "%d", (int)EXTRACT_STYPE_IMM (l));
+         break;
+
+       case 'f':
          print (info->stream, "%d", (int)EXTRACT_STYPE_IMM (l));
          break;
 
@@ -342,6 +448,10 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
          print (info->stream, "%s", riscv_gpr_names[rd]);
          break;
 
+       case 'y':
+         print (info->stream, "0x%x", (int)EXTRACT_OPERAND (BS, l));
+         break;
+
        case 'z':
          print (info->stream, "%s", riscv_gpr_names[0]);
          break;
@@ -407,6 +517,10 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
            break;
          }
 
+       case 'Y':
+         print (info->stream, "0x%x", (int)EXTRACT_OPERAND (RNUM, l));
+         break;
+
        case 'Z':
          print (info->stream, "%d", rs1);
          break;
@@ -414,7 +528,7 @@ print_insn_args (const char *d, insn_t l, bfd_vma pc, disassemble_info *info)
        default:
          /* xgettext:c-format */
          print (info->stream, _("# internal error, undefined modifier (%c)"),
-                *d);
+                *opargStart);
          return;
        }
     }
@@ -482,8 +596,6 @@ riscv_disassemble_insn (bfd_vma memaddr, insn_t word, disassemble_info *info)
   op = riscv_hash[OP_HASH_IDX (word)];
   if (op != NULL)
     {
-      unsigned xlen = 0;
-
       /* If XLEN is not known, get its value from the ELF class.  */
       if (info->mach == bfd_mach_riscv64)
        xlen = 64;
@@ -495,6 +607,10 @@ riscv_disassemble_insn (bfd_vma memaddr, insn_t word, disassemble_info *info)
          xlen = ehdr->e_ident[EI_CLASS] == ELFCLASS64 ? 64 : 32;
        }
 
+      /* If arch has ZFINX flags, use gpr for disassemble.  */
+      if(riscv_subset_supports (&riscv_rps_dis, "zfinx"))
+       riscv_fpr_names = riscv_gpr_names_abi;
+
       for (; op->name; op++)
        {
          /* Does the opcode match?  */
@@ -507,6 +623,9 @@ riscv_disassemble_insn (bfd_vma memaddr, insn_t word, disassemble_info *info)
          if ((op->xlen_requirement != 0) && (op->xlen_requirement != xlen))
            continue;
 
+         if (!riscv_multi_subset_supports (&riscv_rps_dis, op->insn_class))
+           continue;
+
          /* It's a match.  */
          (*info->fprintf_func) (info->stream, "%s", op->name);
          print_insn_args (op->args, word, memaddr, info);
@@ -552,17 +671,235 @@ riscv_disassemble_insn (bfd_vma memaddr, insn_t word, disassemble_info *info)
 
   /* We did not find a match, so just print the instruction bits.  */
   info->insn_type = dis_noninsn;
-  (*info->fprintf_func) (info->stream, "0x%llx", (unsigned long long)word);
+  switch (insnlen)
+    {
+    case 2:
+    case 4:
+    case 8:
+      (*info->fprintf_func) (info->stream, ".%dbyte\t0x%llx",
+                             insnlen, (unsigned long long) word);
+      break;
+    default:
+      {
+        int i;
+        (*info->fprintf_func) (info->stream, ".byte\t");
+        for (i = 0; i < insnlen; ++i)
+          {
+            if (i > 0)
+              (*info->fprintf_func) (info->stream, ", ");
+            (*info->fprintf_func) (info->stream, "0x%02x",
+                                   (unsigned int) (word & 0xff));
+            word >>= 8;
+          }
+      }
+      break;
+    }
   return insnlen;
 }
 
+/* Return true if we find the suitable mapping symbol,
+   and also update the STATE.  Otherwise, return false.  */
+
+static bool
+riscv_get_map_state (int n,
+                    enum riscv_seg_mstate *state,
+                    struct disassemble_info *info)
+{
+  const char *name;
+
+  /* If the symbol is in a different section, ignore it.  */
+  if (info->section != NULL
+      && info->section != info->symtab[n]->section)
+    return false;
+
+  name = bfd_asymbol_name(info->symtab[n]);
+  if (strcmp (name, "$x") == 0)
+    *state = MAP_INSN;
+  else if (strcmp (name, "$d") == 0)
+    *state = MAP_DATA;
+  else
+    return false;
+
+  return true;
+}
+
+/* Check the sorted symbol table (sorted by the symbol value), find the
+   suitable mapping symbols.  */
+
+static enum riscv_seg_mstate
+riscv_search_mapping_symbol (bfd_vma memaddr,
+                            struct disassemble_info *info)
+{
+  enum riscv_seg_mstate mstate;
+  bool from_last_map_symbol;
+  bool found = false;
+  int symbol = -1;
+  int n;
+
+  /* Decide whether to print the data or instruction by default, in case
+     we can not find the corresponding mapping symbols.  */
+  mstate = MAP_DATA;
+  if ((info->section
+       && info->section->flags & SEC_CODE)
+      || !info->section)
+    mstate = MAP_INSN;
+
+  if (info->symtab_size == 0
+      || bfd_asymbol_flavour (*info->symtab) != bfd_target_elf_flavour)
+    return mstate;
+
+  /* Reset the last_map_symbol if we start to dump a new section.  */
+  if (memaddr <= 0)
+    last_map_symbol = -1;
+
+  /* If the last stop offset is different from the current one, then
+     don't use the last_map_symbol to search.  We usually reset the
+     info->stop_offset when handling a new section.  */
+  from_last_map_symbol = (last_map_symbol >= 0
+                         && info->stop_offset == last_stop_offset);
+
+  /* Start scanning at the start of the function, or wherever
+     we finished last time.  */
+  n = info->symtab_pos + 1;
+  if (from_last_map_symbol && n >= last_map_symbol)
+    n = last_map_symbol;
+
+  /* Find the suitable mapping symbol to dump.  */
+  for (; n < info->symtab_size; n++)
+    {
+      bfd_vma addr = bfd_asymbol_value (info->symtab[n]);
+      /* We have searched all possible symbols in the range.  */
+      if (addr > memaddr)
+       break;
+      if (riscv_get_map_state (n, &mstate, info))
+       {
+         symbol = n;
+         found = true;
+         /* Do not stop searching, in case there are some mapping
+            symbols have the same value, but have different names.
+            Use the last one.  */
+       }
+    }
+
+  /* We can not find the suitable mapping symbol above.  Therefore, we
+     look forwards and try to find it again, but don't go pass the start
+     of the section.  Otherwise a data section without mapping symbols
+     can pick up a text mapping symbol of a preceeding section.  */
+  if (!found)
+    {
+      n = info->symtab_pos;
+      if (from_last_map_symbol && n >= last_map_symbol)
+       n = last_map_symbol;
+
+      for (; n >= 0; n--)
+       {
+         bfd_vma addr = bfd_asymbol_value (info->symtab[n]);
+         /* We have searched all possible symbols in the range.  */
+         if (addr < (info->section ? info->section->vma : 0))
+           break;
+         /* Stop searching once we find the closed mapping symbol.  */
+         if (riscv_get_map_state (n, &mstate, info))
+           {
+             symbol = n;
+             found = true;
+             break;
+           }
+       }
+    }
+
+  /* Save the information for next use.  */
+  last_map_symbol = symbol;
+  last_stop_offset = info->stop_offset;
+
+  return mstate;
+}
+
+/* Decide which data size we should print.  */
+
+static bfd_vma
+riscv_data_length (bfd_vma memaddr,
+                  disassemble_info *info)
+{
+  bfd_vma length;
+  bool found = false;
+
+  length = 4;
+  if (info->symtab_size != 0
+      && bfd_asymbol_flavour (*info->symtab) == bfd_target_elf_flavour
+      && last_map_symbol >= 0)
+    {
+      int n;
+      enum riscv_seg_mstate m = MAP_NONE;
+      for (n = last_map_symbol + 1; n < info->symtab_size; n++)
+       {
+         bfd_vma addr = bfd_asymbol_value (info->symtab[n]);
+         if (addr > memaddr
+             && riscv_get_map_state (n, &m, info))
+           {
+             if (addr - memaddr < length)
+               length = addr - memaddr;
+             found = true;
+             break;
+           }
+       }
+    }
+  if (!found)
+    {
+      /* Do not set the length which exceeds the section size.  */
+      bfd_vma offset = info->section->vma + info->section->size;
+      offset -= memaddr;
+      length = (offset < length) ? offset : length;
+    }
+  length = length == 3 ? 2 : length;
+  return length;
+}
+
+/* Dump the data contents.  */
+
+static int
+riscv_disassemble_data (bfd_vma memaddr ATTRIBUTE_UNUSED,
+                       insn_t data,
+                       disassemble_info *info)
+{
+  info->display_endian = info->endian;
+
+  switch (info->bytes_per_chunk)
+    {
+    case 1:
+      info->bytes_per_line = 6;
+      (*info->fprintf_func) (info->stream, ".byte\t0x%02llx",
+                            (unsigned long long) data);
+      break;
+    case 2:
+      info->bytes_per_line = 8;
+      (*info->fprintf_func) (info->stream, ".short\t0x%04llx",
+                            (unsigned long long) data);
+      break;
+    case 4:
+      info->bytes_per_line = 8;
+      (*info->fprintf_func) (info->stream, ".word\t0x%08llx",
+                            (unsigned long long) data);
+      break;
+    case 8:
+      info->bytes_per_line = 8;
+      (*info->fprintf_func) (info->stream, ".dword\t0x%016llx",
+                            (unsigned long long) data);
+      break;
+    default:
+      abort ();
+    }
+  return info->bytes_per_chunk;
+}
+
 int
 print_insn_riscv (bfd_vma memaddr, struct disassemble_info *info)
 {
-  bfd_byte packet[2];
+  bfd_byte packet[8];
   insn_t insn = 0;
-  bfd_vma n;
+  bfd_vma dump_size;
   int status;
+  enum riscv_seg_mstate mstate;
+  int (*riscv_disassembler) (bfd_vma, insn_t, struct disassemble_info *);
 
   if (info->disassembler_options != NULL)
     {
@@ -573,33 +910,54 @@ print_insn_riscv (bfd_vma memaddr, struct disassemble_info *info)
   else if (riscv_gpr_names == NULL)
     set_default_riscv_dis_options ();
 
-  /* Instructions are a sequence of 2-byte packets in little-endian order.  */
-  for (n = 0; n < sizeof (insn) && n < riscv_insn_length (insn); n += 2)
+  mstate = riscv_search_mapping_symbol (memaddr, info);
+  /* Save the last mapping state.  */
+  last_map_state = mstate;
+
+  /* Set the size to dump.  */
+  if (mstate == MAP_DATA
+      && (info->flags & DISASSEMBLE_DATA) == 0)
     {
-      status = (*info->read_memory_func) (memaddr + n, packet, 2, info);
+      dump_size = riscv_data_length (memaddr, info);
+      info->bytes_per_chunk = dump_size;
+      riscv_disassembler = riscv_disassemble_data;
+    }
+  else
+    {
+      /* Get the first 2-bytes to check the lenghth of instruction.  */
+      status = (*info->read_memory_func) (memaddr, packet, 2, info);
       if (status != 0)
        {
-         /* Don't fail just because we fell off the end.  */
-         if (n > 0)
-           break;
          (*info->memory_error_func) (status, memaddr, info);
          return status;
        }
+      insn = (insn_t) bfd_getl16 (packet);
+      dump_size = riscv_insn_length (insn);
+      riscv_disassembler = riscv_disassemble_insn;
+    }
 
-      insn |= ((insn_t) bfd_getl16 (packet)) << (8 * n);
+  /* Fetch the instruction to dump.  */
+  status = (*info->read_memory_func) (memaddr, packet, dump_size, info);
+  if (status != 0)
+    {
+      (*info->memory_error_func) (status, memaddr, info);
+      return status;
     }
+  insn = (insn_t) bfd_get_bits (packet, dump_size * 8, false);
 
-  return riscv_disassemble_insn (memaddr, insn, info);
+  return (*riscv_disassembler) (memaddr, insn, info);
 }
 
 disassembler_ftype
 riscv_get_disassembler (bfd *abfd)
 {
+  const char *default_arch = "rv64gc";
+
   if (abfd)
     {
       const struct elf_backend_data *ebd = get_elf_backend_data (abfd);
       if (ebd)
-        {
+       {
          const char *sec_name = ebd->obj_attrs_section;
          if (bfd_get_section_by_name (abfd, sec_name) != NULL)
            {
@@ -611,10 +969,14 @@ riscv_get_disassembler (bfd *abfd)
                                                      attr[Tag_b].i,
                                                      attr[Tag_c].i,
                                                      &default_priv_spec);
+             default_arch = attr[Tag_RISCV_arch].s;
            }
-        }
+       }
     }
-   return print_insn_riscv;
+
+  riscv_release_subset_list (&riscv_subsets);
+  riscv_parse_subset (&riscv_rps_dis, default_arch);
+  return print_insn_riscv;
 }
 
 /* Prevent use of the fake labels that are generated as part of the DWARF
@@ -631,26 +993,156 @@ riscv_symbol_is_valid (asymbol * sym,
 
   name = bfd_asymbol_name (sym);
 
-  return (strcmp (name, RISCV_FAKE_LABEL_NAME) != 0);
+  return (strcmp (name, RISCV_FAKE_LABEL_NAME) != 0
+         && !riscv_elf_is_mapping_symbols (name));
+}
+\f
+
+/* Indices into option argument vector for options accepting an argument.
+   Use RISCV_OPTION_ARG_NONE for options accepting no argument.  */
+
+typedef enum
+{
+  RISCV_OPTION_ARG_NONE = -1,
+  RISCV_OPTION_ARG_PRIV_SPEC,
+
+  RISCV_OPTION_ARG_COUNT
+} riscv_option_arg_t;
+
+/* Valid RISCV disassembler options.  */
+
+static struct
+{
+  const char *name;
+  const char *description;
+  riscv_option_arg_t arg;
+} riscv_options[] =
+{
+  { "numeric",
+    N_("Print numeric register names, rather than ABI names."),
+    RISCV_OPTION_ARG_NONE },
+  { "no-aliases",
+    N_("Disassemble only into canonical instructions."),
+    RISCV_OPTION_ARG_NONE },
+  { "priv-spec=",
+    N_("Print the CSR according to the chosen privilege spec."),
+    RISCV_OPTION_ARG_PRIV_SPEC }
+};
+
+/* Build the structure representing valid RISCV disassembler options.
+   This is done dynamically for maintenance ease purpose; a static
+   initializer would be unreadable.  */
+
+const disasm_options_and_args_t *
+disassembler_options_riscv (void)
+{
+  static disasm_options_and_args_t *opts_and_args;
+
+  if (opts_and_args == NULL)
+    {
+      size_t num_options = ARRAY_SIZE (riscv_options);
+      size_t num_args = RISCV_OPTION_ARG_COUNT;
+      disasm_option_arg_t *args;
+      disasm_options_t *opts;
+      size_t i, priv_spec_count;
+
+      args = XNEWVEC (disasm_option_arg_t, num_args + 1);
+
+      args[RISCV_OPTION_ARG_PRIV_SPEC].name = "SPEC";
+      priv_spec_count = PRIV_SPEC_CLASS_DRAFT - PRIV_SPEC_CLASS_NONE - 1;
+      args[RISCV_OPTION_ARG_PRIV_SPEC].values
+        = XNEWVEC (const char *, priv_spec_count + 1);
+      for (i = 0; i < priv_spec_count; i++)
+       args[RISCV_OPTION_ARG_PRIV_SPEC].values[i]
+          = riscv_priv_specs[i].name;
+      /* The array we return must be NULL terminated.  */
+      args[RISCV_OPTION_ARG_PRIV_SPEC].values[i] = NULL;
+
+      /* The array we return must be NULL terminated.  */
+      args[num_args].name = NULL;
+      args[num_args].values = NULL;
+
+      opts_and_args = XNEW (disasm_options_and_args_t);
+      opts_and_args->args = args;
+
+      opts = &opts_and_args->options;
+      opts->name = XNEWVEC (const char *, num_options + 1);
+      opts->description = XNEWVEC (const char *, num_options + 1);
+      opts->arg = XNEWVEC (const disasm_option_arg_t *, num_options + 1);
+      for (i = 0; i < num_options; i++)
+       {
+         opts->name[i] = riscv_options[i].name;
+         opts->description[i] = _(riscv_options[i].description);
+         if (riscv_options[i].arg != RISCV_OPTION_ARG_NONE)
+           opts->arg[i] = &args[riscv_options[i].arg];
+         else
+           opts->arg[i] = NULL;
+       }
+      /* The array we return must be NULL terminated.  */
+      opts->name[i] = NULL;
+      opts->description[i] = NULL;
+      opts->arg[i] = NULL;
+    }
+
+  return opts_and_args;
 }
 
 void
 print_riscv_disassembler_options (FILE *stream)
 {
+  const disasm_options_and_args_t *opts_and_args;
+  const disasm_option_arg_t *args;
+  const disasm_options_t *opts;
+  size_t max_len = 0;
+  size_t i;
+  size_t j;
+
+  opts_and_args = disassembler_options_riscv ();
+  opts = &opts_and_args->options;
+  args = opts_and_args->args;
+
   fprintf (stream, _("\n\
-The following RISC-V-specific disassembler options are supported for use\n\
+The following RISC-V specific disassembler options are supported for use\n\
 with the -M switch (multiple options should be separated by commas):\n"));
+  fprintf (stream, "\n");
 
-  fprintf (stream, _("\n\
-  numeric         Print numeric register names, rather than ABI names.\n"));
+  /* Compute the length of the longest option name.  */
+  for (i = 0; opts->name[i] != NULL; i++)
+    {
+      size_t len = strlen (opts->name[i]);
 
-  fprintf (stream, _("\n\
-  no-aliases      Disassemble only into canonical instructions, rather\n\
-                  than into pseudoinstructions.\n"));
+      if (opts->arg[i] != NULL)
+       len += strlen (opts->arg[i]->name);
+      if (max_len < len)
+       max_len = len;
+    }
 
-  fprintf (stream, _("\n\
-  priv-spec=PRIV  Print the CSR according to the chosen privilege spec\n\
-                  (1.9, 1.9.1, 1.10, 1.11).\n"));
+  for (i = 0, max_len++; opts->name[i] != NULL; i++)
+    {
+      fprintf (stream, "  %s", opts->name[i]);
+      if (opts->arg[i] != NULL)
+       fprintf (stream, "%s", opts->arg[i]->name);
+      if (opts->description[i] != NULL)
+       {
+         size_t len = strlen (opts->name[i]);
+
+         if (opts->arg != NULL && opts->arg[i] != NULL)
+           len += strlen (opts->arg[i]->name);
+         fprintf (stream, "%*c %s", (int) (max_len - len), ' ',
+                   opts->description[i]);
+       }
+      fprintf (stream, "\n");
+    }
+
+  for (i = 0; args[i].name != NULL; i++)
+    {
+      fprintf (stream, _("\n\
+  For the options above, the following values are supported for \"%s\":\n   "),
+              args[i].name);
+      for (j = 0; args[i].values[j] != NULL; j++)
+       fprintf (stream, " %s", args[i].values[j]);
+      fprintf (stream, _("\n"));
+    }
 
   fprintf (stream, _("\n"));
 }