[2/2] RISC-V: Add ARC-V APEX disassembler support

Message ID 20260525133052.1885254-3-luiss@synopsys.com
State New
Headers
Series RISC-V: Add ARC-V APEX assembler and disassembler support |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Luis Silva May 25, 2026, 1:30 p.m. UTC
  Add disassembler support for ARC-V APEX instructions by reading metadata from
.riscvapex.* ELF sections (emitted by GAS) and reconstructing instruction
definitions at startup.

Each metadata record is deserialized into a dynamically allocated riscv_opcode
entry and placed in the arcv_apex_insns[] lookup table, indexed by format offset
plus sub-opcode.

When the standard opcode table produces no match, the disassembler determines
the instruction format from bits [14:12], extracts the sub-opcode, and looks up
the corresponding entry.

gas/ChangeLog:

        * testsuite/gas/riscv/x-arcv-apex-01.d: Add disassembly
        expected output.
        * testsuite/gas/riscv/x-arcv-apex-02.d: Likewise.
        * testsuite/gas/riscv/x-arcv-apex-04.d: Likewise.

include/ChangeLog:

        * opcode/riscv.h (EXTRACT_ARCV_APEX_XS_IMM): New macro.
        (EXTRACT_ARCV_APEX_XI_XC_IMM): Likewise.

ld/ChangeLog:

        * testsuite/ld-riscv-elf/x-arcv-apex-02.d: New test.

opcodes/ChangeLog:

	* riscv-dis.c (arcv_apex_free_instructions): New function.
        (print_insn_args): Add 'Xa' vendor operand handling.
        (arcv_apex_xd_type_p): New function.
        (arcv_apex_xs_type_p): Likewise.
        (arcv_apex_xi_type_p): Likewise.
        (arcv_apex_xc_type_p): Likewise.
        (arcv_apex_decode_insn): New function.
        (riscv_disassemble_insn): Try APEX decode after static table.
        (arcv_apex_read_metadata): New function.
        (riscv_init_disasm_info): Read APEX metadata from ELF sections.

Co-Authored-By: Alex Turjan <turjan@synopsys.com>
Signed-off-by: Luis Silva <luiss@synopsys.com>
---
 gas/testsuite/gas/riscv/x-arcv-apex-01.d   |  18 +-
 gas/testsuite/gas/riscv/x-arcv-apex-02.d   |   8 +-
 gas/testsuite/gas/riscv/x-arcv-apex-04.d   |  12 +-
 include/opcode/riscv.h                     |   6 +
 ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d |  36 +--
 opcodes/riscv-dis.c                        | 269 +++++++++++++++++++++
 6 files changed, 312 insertions(+), 37 deletions(-)
  

Patch

diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-01.d b/gas/testsuite/gas/riscv/x-arcv-apex-01.d
index e3faa8bd632..621241d8674 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-01.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-01.d
@@ -7,12 +7,12 @@ 
 Disassembly of section .text:
 
 0+[0-9a-f]+ <.text>:
-[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+.*
+[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+foo0[ 	]+a0,a1,a2
+[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+foo1[ 	]+a1,a2
+[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+foo2[ 	]+a1
+[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+foo3
+[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+foo4[ 	]+a0,a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+foo5[ 	]+a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+foo6[ 	]+a0,(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+foo7[ 	]+(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+foo8[ 	]+a0,a0,(0x21|33)
diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-02.d b/gas/testsuite/gas/riscv/x-arcv-apex-02.d
index 6bb08202e2a..d5fa36ff77f 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-02.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-02.d
@@ -7,7 +7,7 @@ 
 Disassembly of section .text:
 
 0+[0-9a-f]+ <.text>:
-[ 	]+[0-9a-f]+:[ 	]+0020e50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0205b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+1000e50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0ff0e50b[ 	]+.*
+[ 	]+[0-9a-f]+:[ 	]+0020e50b[ 	]+foo[ 	]+a0,a0,(0x2|2)
+[ 	]+[0-9a-f]+:[ 	]+0205b50b[ 	]+foo[ 	]+a0,a1,(0x2|2)
+[ 	]+[0-9a-f]+:[ 	]+1000e50b[ 	]+foo[ 	]+a0,a0,(0x100|256)
+[ 	]+[0-9a-f]+:[ 	]+0ff0e50b[ 	]+foo[ 	]+a0,a0,(0xff|255)
diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-04.d b/gas/testsuite/gas/riscv/x-arcv-apex-04.d
index f62d160a30b..a9461929b37 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-04.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-04.d
@@ -7,9 +7,9 @@ 
 Disassembly of section .text:
 
 0+[0-9a-f]+ <.text>:
-[ 	]+[0-9a-f]+:[ 	]+8005b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+7f05b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+8000a50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+7ff0a50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+8000e50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+7ff0e50b[ 	]+.*
+[ 	]+[0-9a-f]+:[ 	]+8005b50b[ 	]+foo0[ 	]+a0,a1,(-0x80|-128)
+[ 	]+[0-9a-f]+:[ 	]+7f05b50b[ 	]+foo0[ 	]+a0,a1,(0x7f|127)
+[ 	]+[0-9a-f]+:[ 	]+8000a50b[ 	]+foo1[ 	]+a0,(-0x800|-2048)
+[ 	]+[0-9a-f]+:[ 	]+7ff0a50b[ 	]+foo1[ 	]+a0,(0x7ff|2047)
+[ 	]+[0-9a-f]+:[ 	]+8000e50b[ 	]+foo2[ 	]+a0,a0,(-0x800|-2048)
+[ 	]+[0-9a-f]+:[ 	]+7ff0e50b[ 	]+foo2[ 	]+a0,a0,(0x7ff|2047)
diff --git a/include/opcode/riscv.h b/include/opcode/riscv.h
index fdb23c0958d..80cd0622683 100644
--- a/include/opcode/riscv.h
+++ b/include/opcode/riscv.h
@@ -794,6 +794,12 @@  struct apex_insn
 /* Metadata record type for APEX instructions.  */
 #define ARCV_APEX_METADATA_TYPE   1
 
+#define EXTRACT_ARCV_APEX_XS_IMM(x) \
+  (RV_X(x, 24, 8) | (RV_IMM_SIGN(x) << 8))
+
+#define EXTRACT_ARCV_APEX_XI_XC_IMM(x) \
+  (RV_X(x, 20, 12) | (RV_IMM_SIGN(x) << 12))
+
 #define ENCODE_ARCV_APEX_8BIT_IMM(x) \
   (RV_X(x, 0, 8) << 24)
 
diff --git a/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d b/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
index abebb3566e6..317417795de 100644
--- a/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
+++ b/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
@@ -13,24 +13,24 @@  Disassembly of section .text:
 #...
 
 0+[0-9a-f]+ <func_1>:
-[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+.*
+[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+foo0[ 	]+a0,a1,a2
+[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+foo1[ 	]+a1,a2
+[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+foo2[ 	]+a1
+[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+foo3
+[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+foo4[ 	]+a0,a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+foo5[ 	]+a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+foo6[ 	]+a0,(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+foo7[ 	]+(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+foo8[ 	]+a0,a0,(0x21|33)
 
 0+[0-9a-f]+ <func_2>:
-[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+.*
-[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+.*
+[ 	]+[0-9a-f]+:[ 	]+00c5c50b[ 	]+foo0[ 	]+a0,a1,a2
+[ 	]+[0-9a-f]+:[ 	]+02c5800b[ 	]+foo1[ 	]+a1,a2
+[ 	]+[0-9a-f]+:[ 	]+0205c00b[ 	]+foo2[ 	]+a1
+[ 	]+[0-9a-f]+:[ 	]+0400000b[ 	]+foo3
+[ 	]+[0-9a-f]+:[ 	]+0b15b50b[ 	]+foo4[ 	]+a0,a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0b15d00b[ 	]+foo5[ 	]+a1,(0xb|11)
+[ 	]+[0-9a-f]+:[ 	]+0163a50b[ 	]+foo6[ 	]+a0,(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0164200b[ 	]+foo7[ 	]+(0x16|22)
+[ 	]+[0-9a-f]+:[ 	]+0214e50b[ 	]+foo8[ 	]+a0,a0,(0x21|33)
 #pass
diff --git a/opcodes/riscv-dis.c b/opcodes/riscv-dis.c
index eda802ff420..46668046e8d 100644
--- a/opcodes/riscv-dis.c
+++ b/opcodes/riscv-dis.c
@@ -74,6 +74,28 @@  struct riscv_private_data
   bool all_ext;
 };
 
+/* Global instruction table for dynamically-defined APEX extensions.
+   Indexed by format offset (XD, XS, XI, XC) plus sub-opcode.  */
+static struct riscv_opcode *arcv_apex_insns[ARCV_APEX_INSN_LIMIT];
+
+/* Free all dynamically allocated APEX instructions.  */
+
+static void
+arcv_apex_free_instructions (void)
+{
+  unsigned int i;
+
+  for (i = 0; i < ARCV_APEX_INSN_LIMIT; i++)
+    {
+      if (arcv_apex_insns[i] != NULL)
+	{
+	  free ((char *) arcv_apex_insns[i]->name);
+	  free (arcv_apex_insns[i]);
+	  arcv_apex_insns[i] = NULL;
+	}
+    }
+}
+
 /* Set default RISC-V disassembler options.  */
 
 static void
@@ -917,6 +939,33 @@  print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
 		  goto undefined_modifier;
 		}
 	      break;
+	    case 'a': /* Vendor-specific (ARC-V) operands.  */
+	      switch (*++oparg)
+		{
+		case 'd':
+		  print (info->stream, dis_style_register, "%s",
+			 pd->riscv_gpr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
+		  break;
+		case 's':
+		  print (info->stream, dis_style_register, "%s",
+			 pd->riscv_gpr_names[(l >> OP_SH_RS1) & OP_MASK_RS1]);
+		  break;
+		case 't':
+		  print (info->stream, dis_style_register, "%s",
+			 pd->riscv_gpr_names[(l >> OP_SH_RS2) & OP_MASK_RS2]);
+		  break;
+		case 'k':
+		  print (info->stream, dis_style_immediate, "%d",
+			 (int)EXTRACT_ARCV_APEX_XS_IMM (l));
+		  break;
+		case 'j':
+		  print (info->stream, dis_style_immediate, "%d",
+			 (int)EXTRACT_ARCV_APEX_XI_XC_IMM (l));
+		  break;
+		default:
+		  goto undefined_modifier;
+		}
+	      break;
 	    default:
 	      goto undefined_modifier;
 	    }
@@ -933,6 +982,77 @@  print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
     }
 }
 
+/* Check APEX format - bits [14:12] determine the format.  */
+
+static bool
+arcv_apex_xd_type_p (insn_t word)
+{
+  return ((word >> 12) & 0x3) == 0x0;
+}
+
+static bool
+arcv_apex_xs_type_p (insn_t word)
+{
+  return ((word >> 12) & 0x1) == 0x1;
+}
+
+static bool
+arcv_apex_xi_type_p (insn_t word)
+{
+  return ((word >> 12) & 0x7) == 0x2;
+}
+
+static bool
+arcv_apex_xc_type_p (insn_t word)
+{
+  return ((word >> 12) & 0x7) == 0x6;
+}
+
+/* Try to decode WORD as an APEX Custom-0 instruction.
+   Returns the matching opcode entry, or NULL on failure.  */
+
+static const struct riscv_opcode *
+arcv_apex_decode_insn (insn_t word)
+{
+  if ((word & 0xF) != ARCV_APEX_CUSTOM0_OPCODE)
+    return NULL;
+
+  unsigned int offset = 0, sub_opcode = 0;
+
+  if (arcv_apex_xd_type_p (word))
+    {
+      sub_opcode = (((word >> 25) & 0x7F) << 1)
+		 | ((word >> 14) & 1);
+      offset = ARCV_APEX_OFFSET_XD;
+    }
+  else if (arcv_apex_xs_type_p (word))
+    {
+      sub_opcode = (((word >> 20) & 0xF) << 2)
+		 | ((word >> 13) & 0x3);
+      offset = ARCV_APEX_OFFSET_XS;
+    }
+  else if (arcv_apex_xi_type_p (word))
+    {
+      sub_opcode = (word >> 15) & 0x1F;
+      offset = ARCV_APEX_OFFSET_XI;
+    }
+  else if (arcv_apex_xc_type_p (word))
+    {
+      sub_opcode = (word >> 15) & 0x1F;
+      offset = ARCV_APEX_OFFSET_XC;
+    }
+
+  if (sub_opcode + offset >= ARCV_APEX_INSN_LIMIT)
+    return NULL;
+
+  const struct riscv_opcode *op
+    = arcv_apex_insns[sub_opcode + offset];
+  if (op == NULL || !(op->match_func) (op, word))
+    return NULL;
+
+  return op;
+}
+
 /* Print the RISC-V instruction at address MEMADDR in debugged memory,
    on using INFO.  Returns length of the instruction, in bytes.
    BIGENDIAN must be 1 if this is big-endian code, 0 if
@@ -1068,6 +1188,17 @@  riscv_disassemble_insn (bfd_vma memaddr,
 	}
     }
 
+  /* No match in the static table; try APEX metadata-driven Custom-0.  */
+  op = arcv_apex_decode_insn (word);
+  if (op != NULL)
+    {
+      (*info->fprintf_styled_func) (info->stream,
+				    dis_style_mnemonic,
+				    "%s", op->name);
+      print_insn_args (op->args, word, memaddr, info);
+      return insnlen;
+    }
+
   /* We did not find a match, so just print the instruction bits in
      the shape of an assembler .insn directive.  */
   info->insn_type = dis_noninsn;
@@ -1412,6 +1543,123 @@  riscv_disassemble_data (bfd_vma memaddr ATTRIBUTE_UNUSED,
   return info->bytes_per_chunk;
 }
 
+/* Create a mapping between a serialized APEX instruction record
+   (emitted by GAS) and a dynamically allocated riscv_opcode entry.
+
+   Record format (see struct apex_insn):
+     +------+------+--------+--------+-----------+---------+
+     | Len  | Type | Opcode | FuncT  |  Flags    | Name... |
+     +------+------+--------+--------+-----------+---------+
+      1B     1B      1B       1B       2B	  NUL-term
+
+   - 'block' points to the beginning of the serialized record.
+   - 'length' is the size of the block (must match record_len).
+   - The instruction name is stored at the end of the block and
+     is dynamically allocated here.  */
+
+static void
+arcv_apex_read_metadata (unsigned char *block,
+		      unsigned long length)
+{
+  /* Assert that the provided length matches the record length
+     declared at position 0 (as emitted by GAS).  */
+  unsigned int record_len = block[0];
+  if (length != record_len)
+    {
+      opcodes_error_handler (_("APEX metadata: length mismatch "
+			     "(section contents %lu bytes, "
+			     "record header says %u)"),
+			     (unsigned long) length, record_len);
+      return;
+    }
+
+  /* Validate the metadata record type.  */
+  if (block[1] != ARCV_APEX_METADATA_TYPE)
+    {
+      opcodes_error_handler (_("APEX metadata: unknown "
+			     "record type %u"),
+			     (unsigned) block[1]);
+      return;
+    }
+
+  /* Compute the length of the instruction name by
+     subtracting the base struct size.  */
+  size_t name_length = record_len
+    - offsetof (struct apex_insn, name);
+
+  /* Allocate and null-terminate the instruction mnemonic.  */
+  char *name = xmalloc (name_length + 1);
+  memcpy (name,
+	  block + offsetof (struct apex_insn, name), /* Mnemonic start.  */
+	  name_length);
+  name[name_length] = '\0';
+
+  /* Extract sub-opcode and flags (flags is 16-bit, little-endian).  */
+  unsigned int sub_opcode = block[3];
+  unsigned int flags = bfd_getl16 (block + 4);
+
+  /* Allocate a new riscv_opcode entry.  */
+  struct riscv_opcode *insn = XNEW (struct riscv_opcode);
+
+  arcv_apex_init_dynamic_insn (insn);
+  insn->name = name;
+
+  unsigned int offset = 0;
+  /* Decode flags and adjust the opcode entry accordingly.  */
+  switch (flags & 0xF)
+    {
+    case APEX_FLAG_XD:
+      arcv_apex_setup_xd_insn (insn, flags, sub_opcode);
+      offset = ARCV_APEX_OFFSET_XD;
+      break;
+
+    case APEX_FLAG_XS:
+      arcv_apex_setup_xs_insn (insn, flags, sub_opcode);
+      offset = ARCV_APEX_OFFSET_XS;
+      break;
+
+    case APEX_FLAG_XI:
+      arcv_apex_setup_xi_insn (insn, flags, sub_opcode);
+      offset = ARCV_APEX_OFFSET_XI;
+      break;
+
+    case APEX_FLAG_XC:
+      arcv_apex_setup_xc_insn (insn, sub_opcode);
+      offset = ARCV_APEX_OFFSET_XC;
+      break;
+
+    /* If an instruction supports both XS and XC formats
+       (e.g., "extInstruction foo,123,XS,XC"), we create a separate
+       instruction for each format.  This is safe because no other XS
+       or XC instruction can use the same sub-opcode (e.g., =123).  */
+    case (APEX_FLAG_XS | APEX_FLAG_XC):
+      {
+	arcv_apex_setup_xs_insn (insn, flags, sub_opcode);
+	arcv_apex_insns[sub_opcode + ARCV_APEX_OFFSET_XS] = insn;
+
+	struct riscv_opcode *xc_copy = XNEW (struct riscv_opcode);
+	arcv_apex_init_dynamic_insn (xc_copy);
+	xc_copy->name = xstrdup (insn->name);
+	arcv_apex_setup_xc_insn (xc_copy, sub_opcode);
+	arcv_apex_insns[sub_opcode + ARCV_APEX_OFFSET_XC] = xc_copy;
+	return;
+      }
+
+    default:
+      opcodes_error_handler (_("APEX metadata: unknown format flags 0x%x"),
+			     flags & 0xF);
+      free (name);
+      free (insn);
+      return;
+    }
+
+  /* Place the fully initialized APEX instruction in the
+     arcv_apex_insns array at an index determined by its sub-opcode
+     and the format-specific offset.  This ensures each format
+     occupies a distinct array region.  */
+  arcv_apex_insns[sub_opcode + offset] = insn;
+}
+
 static bool
 riscv_init_disasm_info (struct disassemble_info *info)
 {
@@ -1450,6 +1698,27 @@  riscv_init_disasm_info (struct disassemble_info *info)
       bfd *abfd = info->section->owner;
       if (abfd && bfd_get_flavour (abfd) == bfd_target_elf_flavour)
 	{
+	  arcv_apex_free_instructions ();
+
+	  asection *sect;
+	  for (sect = abfd->sections; sect != NULL; sect = sect->next)
+	    {
+	      if (strstr (sect->name, ".riscvapex."))
+		{
+		  bfd_size_type count = bfd_section_size (sect);
+		  unsigned char *buffer = xmalloc (count);
+
+		  if (bfd_get_section_contents (abfd, sect, buffer, 0, count))
+		    arcv_apex_read_metadata (buffer, count);
+		  else
+		    opcodes_error_handler
+		      (_("warning: could not read APEX metadata "
+			 "section `%s'"),
+		       sect->name);
+		  free (buffer);
+		}
+	    }
+
 	  const char *sec_name =
 		get_elf_backend_data (abfd)->obj_attrs_section;
 	  if (bfd_get_section_by_name (abfd, sec_name) != NULL)