[PATCH v8 04/19] gas: implement parsing of object attributes v2

Matthieu Longo matthieu.longo@arm.com
Tue Jul 15 11:39:07 GMT 2025


From: Richard Ball <richard.ball@arm.com>

This patch adds the parsing logic for Object Attributes v2 (OAv2), enabling
Gas to interpret and process these attributes correctly. It also updates the
AArch64 backend to utilize the new parsing capabilities, and handle the new
AArch64-specific directives.

This patch relies on the abstractions introduced in the previous patch to
store the data. Its scope is limited to parsing the new assembly directives,
checking the inputs, and storing the data into the relevant OAv2 abstractions.
Note that, for now, the new parsing capabilities are only available for AArch64.
Even if the implementation was splitted into a generic part available in
gas/config/obj-elf.c, and an AArch64-specific one in gas/config/tc-aarch64.c,
the lack of GNU generic directives to handle OAv2 prevented the capability
from being exposed to others backends.

** GNU assembler interface for aeabi subsections

OAv2 introduced two new directives for AArch64:
- .aeabi_subsection name, comprehension, encoding
  Create or switch the current subsection to 'name'.
  Comprehension values can be 'required' or 'optional'.
  Encoding values are limited for now to 'ULEB128', and 'NTBS'
- .aeabi_attribute tag, value
  Set 'tag' to 'value' in the current subsection.
  Tag can either be an integer, or one of the defined symbols in the backend.

The usage of those directives will error if the following requirements
are breached:
- If the subsection X has been previously declared, the comprehension and
  encoding parameters of the current .aeabi_subsection that redeclares X
  have to match with the previous declaration.
- The type of the value set via .aeabi_attribute has to align with
  the current subsection.
- If the tag N has already been declared for the current subsection,
  a later assignment to tag N is tolerated only if the newly set value
  is equal to the former one.

The new parsing code is enabled/disabled via the TC_OBJ_ATTR_v1 and
TC_OBJ_ATTR_v2 defines, and supports the following configurations:
- enable both OAv1 and OAv2 parsing. This is currently used by no
  target, but is useful for migration from OAv1 to OAv2.
- enable OAv1 parsing only. This is used by all targets supporting OAs
  except for AArch64.
- enable OAv2 parsing only. This is only used by AArch64.

** Regarding the implementation

The logic of OAv1 does not always keep separated the different data processing
steps: parsing, convertion to internal abstractions, error checking and further
processing (if any) on those abstractions, and their serialization into the
object files.
This patch takes into account the specifities of syntax for OAv1 and OAv2, but
mutualize as much as possible the common behavior so that the same methods can
be used for parsing the OAv1 and OAv2 directives.
However, the mutualization of the code is limited by a different internal model
for OAv1 and OAv2. Even if it is technically feaseable to have only one middle
-end for OAv1, OAv2 and even GNU properties, sharing the same data model to
perform the merge logic with the same code, it is a significant amount of work.
This extra work was not considered as a part of this new feature, so this patch
series will stick with the minimum of mutualization as possible.

Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
---
 bfd/elf-attrs.c           | 146 +++++-
 bfd/elf-attrs.h           |  77 ++++
 bfd/elf-bfd.h             |   7 +
 bfd/elfnn-aarch64.c       |   6 +
 bfd/elfxx-aarch64.c       |  73 +++
 bfd/elfxx-aarch64.h       |   2 +
 bfd/elfxx-target.h        |   8 +
 gas/config/obj-elf-attr.c | 949 ++++++++++++++++++++++++++++++++++++++
 gas/config/obj-elf-attr.h |  24 +
 gas/config/obj-elf.c      |  17 +-
 gas/config/tc-aarch64.c   |  16 +
 gas/config/tc-aarch64.h   |   5 +
 gas/config/tc-arc.h       |   4 +-
 gas/config/tc-arm.h       |   6 +-
 gas/config/tc-csky.h      |   4 +-
 gas/config/tc-m68k.h      |   4 +-
 gas/config/tc-mips.h      |   4 +-
 gas/config/tc-msp430.h    |   4 +-
 gas/config/tc-ppc.h       |   6 +-
 gas/config/tc-riscv.h     |   4 +-
 gas/config/tc-s390.h      |   4 +-
 gas/config/tc-sparc.h     |   4 +-
 gas/config/tc-tic6x.h     |   4 +-
 gas/configure             |   2 +-
 gas/configure.ac          |   2 +-
 gas/doc/c-aarch64.texi    |  27 ++
 include/elf/aarch64.h     |  13 +
 27 files changed, 1393 insertions(+), 29 deletions(-)

diff --git a/bfd/elf-attrs.c b/bfd/elf-attrs.c
index fb3ad33a47f..8c6b2910f70 100644
--- a/bfd/elf-attrs.c
+++ b/bfd/elf-attrs.c
@@ -255,8 +255,152 @@ bfd_elf_set_obj_attr_contents (bfd *abfd, bfd_byte *buffer, bfd_vma size)
   write_obj_attr_section_v1 (abfd, buffer, size);
 }
 
+/* The first two tags in gnu-testing namespace are known, and so have a name and
+   can be initialized to the default value ('0' or NULL) depending on the
+   encoding specified on the subsection.  Any tags above 1 will be considered
+   unknown, so will be default initialized in the same way but its status will
+   be set to obj_attr_subsection_v2_unknown.  */
+static const obj_attr_info_t known_tags_gnu_testing[] =
+{
+  {
+    .tag = {"GNUTestTag_0", .value = {
+      .val.u32 = 0,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u64 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+  {
+    .tag = {"GNUTestTag_1", .value = {
+      .val.u32 = 1,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u64 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+};
+
+/* List of known GNU subsections.
+   Note: this list needs to be sorted.  */
+static known_subsection_v2 obj_attr_v2_known_gnu_subsections[] =
+{
+  {
+    /* Note: the currently set values for the subsection name, its optionality,
+       and encoding are irrelevant for a testing subsection.  These values are
+       unused.  This entry is only a placeholder for list of known GNU testing
+       tags.  */
+    .subsec_name = NULL,
+    .known_tags = known_tags_gnu_testing,
+    .optional = true,
+    .encoding = OA_ENC_ULEB128,
+    .len = sizeof (known_tags_gnu_testing) / sizeof (obj_attr_info_t),
+  },
+  /* Note for the future: GNU subsections can be added here below.  */
+};
+
+/* Return True if the given subsection name is part of the reserved "gnu-testing"
+   namespace.  */
+static bool
+gnu_testing_namespace (const char *subsec_name)
+{
+  return strncmp ("gnu-testing", subsec_name, 11) == 0;
+}
+
+/* Search for a subsection matching NAME in the list of subsections known from
+   bfd (generic or backend-specific).  Return the subsection information if it
+   is found, or NULL otherwise.  */
+const known_subsection_v2 *
+identify_subsection (const struct elf_backend_data *be,
+		     const char *name)
+{
+  /* Check known backend subsections.  */
+  const known_subsection_v2 *known_subsections = be->obj_attr_v2_known_subsections;
+  const size_t known_subsections_size = be->obj_attr_v2_known_subsections_size;
+
+  for (unsigned i = 0; i < known_subsections_size; ++i)
+    {
+      int cmp = strcmp (known_subsections[i].subsec_name, name);
+      if (cmp == 0)
+	return &known_subsections[i];
+      else if (cmp > 0)
+	break;
+    }
+
+  /* Check known GNU subsections.  */
+  /* Note for the future: search known GNU subsections here.  Don't forget to
+     skip the first entry (placeholder for GNU testing subsection).  */
+
+  /* Check whether this subsection is a GNU testing subsection.  */
+  if (gnu_testing_namespace (name))
+    return &obj_attr_v2_known_gnu_subsections[0];
+
+  return NULL;
+}
+
+/* Search for the attribute information associated to TAG in the list of known
+   tags registered in the known subsection SUBSEC.  Return the tag information
+   if it is found, NULL otherwise.  */
+static const obj_attr_info_t *
+identify_tag (const known_subsection_v2 *subsec, obj_attr_tag_t tag)
+{
+  for (unsigned i = 0; i < subsec->len; ++i)
+    {
+      const obj_attr_info_t *known_tag = &subsec->known_tags[i];
+      if (known_tag->tag.value.val.u32 == tag)
+	return known_tag;
+      else if (known_tag->tag.value.val.u32 > tag)
+	break;
+    }
+  return NULL;
+}
+
+/* Return the attribute information associated to the pair SUBSEC, TAG if it
+   exists, NULL otherwise.  */
+const obj_attr_info_t *
+known_obj_attr_v2_find_by_tag (const struct elf_backend_data *be,
+			       const char *subsec_name,
+			       obj_attr_tag_t tag)
+{
+  const known_subsection_v2 *subsec_info
+    = identify_subsection (be, subsec_name);
+  if (subsec_info != NULL)
+    {
+      const obj_attr_info_t *tag_info = identify_tag (subsec_info, tag);
+      return tag_info;
+    }
+  return NULL;
+}
+
+/* To-string function for the pair <SUBSEC, TAG>.  Returns the identifier
+   associated to TAG if it is found, NULL otherwise.  */
+const char *
+obj_attr_v2_tag_to_string (const struct elf_backend_data *be,
+			   const char *subsec_name,
+			   obj_attr_tag_t tag)
+{
+  const obj_attr_info_t *tag_info
+    = known_obj_attr_v2_find_by_tag (be, subsec_name, tag);
+  if (tag_info != NULL)
+    return tag_info->tag.identifier;
+  return NULL;
+}
+
+/* To-string function for the subsection parameter "comprehension".  */
+const char *
+oav2_comprehension_to_string (bool comprehension)
+{
+  return comprehension ? "optional" : "required";
+}
+
+/* To-string function for the subsection parameter "encoding".  */
+const char *
+oav2_encoding_to_string (obj_attr_encoding_v2 encoding)
+{
+  return (encoding == OA_ENC_ULEB128) ? "ULEB128" : "NTBS";
+}
+
 /* Allocate/find an object attribute.  */
-static obj_attribute *
+obj_attribute *
 elf_new_obj_attr (bfd *abfd, obj_attr_vendor_t vendor, obj_attr_tag_t tag)
 {
   obj_attribute *attr;
diff --git a/bfd/elf-attrs.h b/bfd/elf-attrs.h
index cbd8ad31daf..103ed1cf45d 100644
--- a/bfd/elf-attrs.h
+++ b/bfd/elf-attrs.h
@@ -47,6 +47,9 @@ typedef enum obj_attr_encoding_v2
 #define obj_attr_encoding_v2_to_u8(value) \
   ((uint8_t) (value - 1))
 
+extern const char *
+oav2_encoding_to_string (obj_attr_encoding_v2);
+
 typedef union obj_attr_value_v2 {
   uint32_t uint_val;
   const char* string_val;
@@ -105,6 +108,9 @@ typedef struct obj_attr_subsection_v2 {
 
 } obj_attr_subsection_v2;
 
+extern const char *
+oav2_comprehension_to_string (bool);
+
 typedef struct obj_attr_subsection_list
 {
   /* A pointer to the first node of the list.  */
@@ -116,3 +122,74 @@ typedef struct obj_attr_subsection_list
   /* The size of the list.  */
   uint32_t size;
 } obj_attr_subsection_list;
+
+/* Basic implementation of a variant for the possible types associated to an
+   object attribute.  */
+struct gas_variant_t;
+
+typedef struct {
+  size_t len;
+  struct gas_variant_t *elts;
+} gas_variant_list;
+
+typedef union {
+  const char *string;
+  uint8_t u8;
+  uint32_t u32;
+  uint64_t u64;
+  int64_t i64;
+  bool b;
+  gas_variant_list list;
+} gas_variant_value;
+
+typedef enum {
+  VALUE_UNDEFINED = 0,
+  VALUE_U8,
+  VALUE_U32,
+  VALUE_U64,
+  VALUE_I64,
+  VALUE_UNSIGNED_INTEGER = VALUE_U64,
+  VALUE_SIGNED_INTEGER = VALUE_I64,
+  VALUE_BOOL,
+  VALUE_STRING,
+  VALUE_LIST,
+} gas_variant_type_info;
+
+typedef struct gas_variant_t {
+  gas_variant_value val;
+  gas_variant_type_info vtype;
+} gas_variant_t;
+
+typedef struct {
+  const char *const identifier;
+  const gas_variant_t value;
+} gas_symbol_t;
+
+/* Attribute information.  */
+typedef struct {
+  const gas_symbol_t tag;
+  const gas_variant_t default_value;
+  const obj_attr_encoding_v2 encoding;
+} obj_attr_info_t;
+
+typedef struct
+{
+  const char *const subsec_name;
+  const obj_attr_info_t *known_tags;
+  const bool optional;
+  const obj_attr_encoding_v2 encoding;
+  const size_t len;
+} known_subsection_v2;
+
+struct elf_backend_data;
+
+extern const known_subsection_v2 *
+identify_subsection (const struct elf_backend_data *, const char*);
+
+extern const obj_attr_info_t *
+known_obj_attr_v2_find_by_tag (const struct elf_backend_data *,
+			       const char*, obj_attr_tag_t);
+
+extern const char *
+obj_attr_v2_tag_to_string (const struct elf_backend_data *, const char*,
+			   obj_attr_tag_t);
diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 5a3a6f91918..498635aa30c 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -1657,6 +1657,12 @@ struct elf_backend_data
   /* Encode the object attributes version into the output object.  */
   uint8_t (*obj_attrs_version_enc) (obj_attr_version_t);
 
+  /* The known subsections and attributes (v2 only).  */
+  const known_subsection_v2 *obj_attr_v2_known_subsections;
+
+  /* The size of the array of known subsections.  */
+  const size_t obj_attr_v2_known_subsections_size;
+
   /* This function determines the order in which any attributes are
      written.  It must be defined for input in the range
      LEAST_KNOWN_OBJ_ATTRIBUTE..NUM_KNOWN_OBJ_ATTRIBUTES-1 (this range
@@ -3091,6 +3097,7 @@ extern obj_attr_version_t _bfd_obj_attrs_version_dec (uint8_t);
 extern uint8_t _bfd_obj_attrs_version_enc (obj_attr_version_t);
 extern bfd_vma bfd_elf_obj_attr_size (bfd *);
 extern void bfd_elf_set_obj_attr_contents (bfd *, bfd_byte *, bfd_vma);
+extern obj_attribute * elf_new_obj_attr (bfd *, obj_attr_vendor_t, obj_attr_tag_t);
 extern int bfd_elf_get_obj_attr_int (bfd *, obj_attr_vendor_t, obj_attr_tag_t);
 extern obj_attribute *bfd_elf_add_obj_attr_int
   (bfd *, obj_attr_vendor_t, obj_attr_tag_t, unsigned int);
diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
index 8f1d4a6746f..34797913dd1 100644
--- a/bfd/elfnn-aarch64.c
+++ b/bfd/elfnn-aarch64.c
@@ -10800,6 +10800,12 @@ const struct elf_size_info elfNN_aarch64_size_info =
 #undef	elf_backend_obj_attrs_version_enc
 #define elf_backend_obj_attrs_version_enc \
   _bfd_aarch64_obj_attrs_version_enc
+/* Object attributes v2 specific values.  */
+#undef	elf_backend_obj_attr_v2_known_subsections
+#define elf_backend_obj_attr_v2_known_subsections \
+  aarch64_obj_attr_v2_known_subsections
+#undef	elf_backend_obj_attr_v2_known_subsections_size
+#define elf_backend_obj_attr_v2_known_subsections_size 2
 
 #include "elfNN-target.h"
 
diff --git a/bfd/elfxx-aarch64.c b/bfd/elfxx-aarch64.c
index b34ee13a299..51b20406e55 100644
--- a/bfd/elfxx-aarch64.c
+++ b/bfd/elfxx-aarch64.c
@@ -21,6 +21,7 @@
 #include "sysdep.h"
 #include "bfd.h"
 #include "elf-bfd.h"
+#include "elf/aarch64.h"
 #include "elfxx-aarch64.h"
 #include "libbfd.h"
 #include <stdarg.h>
@@ -887,6 +888,78 @@ _bfd_aarch64_obj_attrs_version_enc (obj_attr_version_t version)
   abort ();
 }
 
+/* Note: this array has to be sorted.  */
+static const obj_attr_info_t known_tags_aeabi_feature_and_bits[] =
+{
+  {
+    .tag = {"Tag_Feature_BTI", .value = {
+      .val.u32 = Tag_Feature_BTI,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u32 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+  {
+    .tag = {"Tag_Feature_PAC", .value = {
+      .val.u32 = Tag_Feature_PAC,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u32 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+  {
+    .tag = {"Tag_Feature_GCS", .value = {
+      .val.u32 = Tag_Feature_GCS,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u32 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+};
+
+/* This is a required subsection to use PAuthABI (which is currently
+   unsupported by GCC). A value of 0 for any the tags below means that
+   the user did not permit this entity to use the PAuthABI.
+   Note: this array has to be sorted.  */
+static const obj_attr_info_t known_tags_aeabi_pauthabi[] =
+{
+  {
+    .tag = {"Tag_PAuth_Platform", .value = {
+      .val.u32 = Tag_PAuth_Platform,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u32 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+  {
+    .tag = {"Tag_PAuth_Schema", .value = {
+      .val.u32 = Tag_PAuth_Schema,
+      .vtype = VALUE_U32
+    }},
+    .default_value = {.val.u32 = 0, .vtype = VALUE_UNSIGNED_INTEGER},
+    .encoding = OA_ENC_ULEB128,
+  },
+};
+
+/* Note: this array is exported by the backend, and needs to be sorted.  */
+const known_subsection_v2 aarch64_obj_attr_v2_known_subsections[] =
+{
+  {
+    .subsec_name = "aeabi_feature_and_bits",
+    .known_tags = known_tags_aeabi_feature_and_bits,
+    .optional = true,
+    .encoding = OA_ENC_ULEB128,
+    .len = sizeof (known_tags_aeabi_feature_and_bits) / sizeof (obj_attr_info_t),
+  },
+  {
+    .subsec_name = "aeabi_pauthabi",
+    .known_tags = known_tags_aeabi_pauthabi,
+    .optional = false,
+    .encoding = OA_ENC_ULEB128,
+    .len = sizeof (known_tags_aeabi_pauthabi) / sizeof (obj_attr_info_t),
+  },
+};
+
 /* Find the first input bfd with GNU property and merge it with GPROP.  If no
    such input is found, add it to a new section at the last input.  Update
    GPROP accordingly.  */
diff --git a/bfd/elfxx-aarch64.h b/bfd/elfxx-aarch64.h
index d23bd1ee6c2..4f41f24f054 100644
--- a/bfd/elfxx-aarch64.h
+++ b/bfd/elfxx-aarch64.h
@@ -240,6 +240,8 @@ _bfd_aarch64_obj_attrs_version_dec (uint8_t);
 extern uint8_t
 _bfd_aarch64_obj_attrs_version_enc (obj_attr_version_t);
 
+extern const known_subsection_v2 aarch64_obj_attr_v2_known_subsections[];
+
 extern bfd *
 _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *);
 
diff --git a/bfd/elfxx-target.h b/bfd/elfxx-target.h
index 2b5515d2c0a..d4903c18e7a 100644
--- a/bfd/elfxx-target.h
+++ b/bfd/elfxx-target.h
@@ -563,6 +563,12 @@
 #ifndef elf_backend_obj_attrs_version_enc
 #define elf_backend_obj_attrs_version_enc	_bfd_obj_attrs_version_enc
 #endif
+#ifndef	elf_backend_obj_attr_v2_known_subsections
+#define elf_backend_obj_attr_v2_known_subsections	NULL
+#endif
+#ifndef	elf_backend_obj_attr_v2_known_subsections_size
+#define elf_backend_obj_attr_v2_known_subsections_size	0
+#endif
 #ifndef elf_backend_obj_attrs_order
 #define elf_backend_obj_attrs_order		NULL
 #endif
@@ -944,6 +950,8 @@ static const struct elf_backend_data elfNN_bed =
   elf_backend_default_obj_attr_version,
   elf_backend_obj_attrs_version_dec,
   elf_backend_obj_attrs_version_enc,
+  elf_backend_obj_attr_v2_known_subsections,
+  elf_backend_obj_attr_v2_known_subsections_size,
   elf_backend_obj_attrs_order,
   elf_backend_obj_attrs_handle_unknown,
   elf_backend_parse_gnu_properties,
diff --git a/gas/config/obj-elf-attr.c b/gas/config/obj-elf-attr.c
index 850db0e37fb..3dfd7388055 100644
--- a/gas/config/obj-elf-attr.c
+++ b/gas/config/obj-elf-attr.c
@@ -22,6 +22,7 @@
 
 #ifdef TC_OBJ_ATTR
 
+#include "obstack.h"
 #include "safe-ctype.h"
 
 #define skip_whitespace(str)  do { if (is_whitespace (*(str))) ++(str); } while (0)
@@ -38,6 +39,8 @@ skip_past_char (char **str, char c)
 }
 #define skip_past_comma(str) skip_past_char (str, ',')
 
+#if (TC_OBJ_ATTR_v1)
+
 /* A list of attributes that have been explicitly set by the assembly code.
    VENDOR is the vendor id, BASE is the tag shifted right by the number
    of bits in MASK, and bit N of MASK is set if tag BASE+N has been set.  */
@@ -120,6 +123,951 @@ oav1_attr_seen (obj_attr_vendor_t vendor, obj_attr_tag_t tag)
   return false;
 }
 
+#endif /* TC_OBJ_ATTR_v1 */
+
+/* Expected argument tokens for object attribute directives.  */
+typedef enum {
+  /* Base types.  */
+  IDENTIFIER = 0x1,
+  UNSIGNED_INTEGER = 0x2,
+  SIGNED_INTEGER = 0x4,
+  STRING = 0x8,
+  LIST = 0x10,
+  LT_MASK = 0xFF,
+  /* Higher types.  */
+  SUBSECTION_NAME = 0x100,
+  SUBSECTION_OPTION_1 = 0x200,
+  SUBSECTION_OPTION_2 = 0x400,
+  ATTRIBUTE_KEY = 0x800,
+  ATTRIBUTE_VALUE = 0x1000,
+  HT_MASK = 0xFF00,
+} arg_token_t;
+
+typedef gas_variant_t arg_t;
+
+/* Free an arguments list of size N.  */
+static void
+args_list_free (arg_t *args, size_t n)
+{
+  for (size_t i = 0; i < n; ++i)
+    if (args[i].vtype == VALUE_STRING)
+      free ((void *) args[i].val.string);
+    else if (args[i].vtype == VALUE_LIST)
+      args_list_free (args[i].val.list.elts, args[i].val.list.len);
+  free (args);
+}
+
+/* Extract a string literal ("[^.]+") from the input.  */
+static bool
+extract_string_literal (arg_t *arg_out)
+{
+  skip_whitespace (input_line_pointer);
+
+  if (*input_line_pointer != '"')
+    {
+      as_bad ("missing '\"', expected a string literal");
+      return false;
+    }
+
+  int len;
+  char *obstack_buf = demand_copy_C_string (&len);
+  if (obstack_buf != NULL)
+    {
+      arg_out->val.string = xstrdup (obstack_buf);
+      obstack_free (&notes, obstack_buf);
+      arg_out->vtype = VALUE_STRING;
+      return true;
+    }
+
+  arg_out->val.string = NULL;
+  return false;
+}
+
+/* Extract an integer literal from the input.
+   Anything matched by O_constant is considered an integer literal (see the
+   usage of O_constant in expr.c to see all the matches.  */
+static bool
+extract_integer_literal (arg_t *arg_out,
+			 bool signedness_unsigned)
+{
+  expressionS exp;
+  expression (&exp);
+  if (exp.X_op != O_constant)
+    {
+      as_bad (_("invalid value, expected an integer literal"));
+      goto bad;
+    }
+
+  int64_t val = exp.X_add_number;
+  if (val < 0 && signedness_unsigned)
+    {
+      as_bad (_("invalid negative value %ld, expected an unsigned integer"),
+	      val);
+      goto bad;
+    }
+  if (signedness_unsigned)
+    {
+      arg_out->val.u64 = val;
+      arg_out->vtype = VALUE_UNSIGNED_INTEGER;
+    }
+  else
+    {
+      arg_out->val.i64 = val;
+      arg_out->vtype = VALUE_SIGNED_INTEGER;
+    }
+  return true;
+
+bad:
+  ignore_rest_of_line ();
+  return false;
+}
+
+/* Extract an identifier based on the provided character matcher.  */
+static bool
+extract_identifier (bool (*char_predicate) (char), arg_t *arg_out)
+{
+  const char *s = input_line_pointer;
+  unsigned int i = 0;
+  for (; char_predicate (*input_line_pointer); ++input_line_pointer)
+    i++;
+  if (i == 0)
+    {
+      as_bad (_("invalid value '%c', expected an identifier"),
+	      *input_line_pointer);
+      ignore_rest_of_line ();
+      return false;
+    }
+
+  char *val = xmemdup0 (s, i);
+  arg_out->vtype = VALUE_STRING;
+  arg_out->val.string = val;
+  return true;
+}
+
+#if (TC_OBJ_ATTR_v2)
+/* Resolve the identifier if it matches the given symbol.  */
+static bool
+resolve_if_matching (const char *identifier,
+		     const gas_symbol_t *symbol,
+		     arg_t *val_out)
+{
+  if (strcmp (symbol->identifier, identifier) != 0)
+    return false;
+
+  /* Free the identifier since we found the value.  */
+  free ((void *) val_out->val.string);
+
+  switch (symbol->value.vtype)
+    {
+    case VALUE_BOOL:
+      val_out->val.u64 = symbol->value.val.b;
+      val_out->vtype = VALUE_UNSIGNED_INTEGER;
+      break;
+    case VALUE_U8:
+      val_out->val.u64 = symbol->value.val.u8;
+      val_out->vtype = VALUE_UNSIGNED_INTEGER;
+      break;
+    case VALUE_U32:
+      val_out->val.u64 = symbol->value.val.u32;
+      val_out->vtype = VALUE_UNSIGNED_INTEGER;
+      break;
+    case VALUE_U64:
+      val_out->val.u64 = symbol->value.val.u64;
+      val_out->vtype = VALUE_UNSIGNED_INTEGER;
+      break;
+    case VALUE_I64:
+      val_out->val.i64 = symbol->value.val.i64;
+      val_out->vtype = VALUE_UNSIGNED_INTEGER;
+      break;
+    case VALUE_STRING:
+      val_out->val.string = strdup (symbol->value.val.string);
+      val_out->vtype = VALUE_STRING;
+      break;
+    default:
+      abort ();
+    }
+
+  return true;
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+#if (TC_OBJ_ATTR_v1)
+/* Look up attribute keys defined in the backend (object attribute v1).  */
+static bool
+obj_attr_v1_lookup_known_attr_key_symbol (const char *identifier,
+					  arg_token_t token_type,
+					  arg_t *val_out)
+{
+#ifndef CONVERT_SYMBOLIC_ATTRIBUTE
+#define CONVERT_SYMBOLIC_ATTRIBUTE(a) -1
+  (void) identifier;
+#endif
+
+  gas_assert (token_type & UNSIGNED_INTEGER);
+
+  int tag = CONVERT_SYMBOLIC_ATTRIBUTE (identifier);
+  if (tag < 0)
+    return false;
+  val_out->val.u64 = tag;
+  val_out->vtype = VALUE_UNSIGNED_INTEGER;
+  return true;
+}
+#endif /* TC_OBJ_ATTR_v1 */
+
+#if (TC_OBJ_ATTR_v2)
+/* Look up attribute keys defined in the backend (object attribute v2).  */
+static bool
+obj_attr_v2_lookup_known_attr_key_symbol (const char *identifier,
+					  arg_token_t token_type,
+					  arg_t *val_out)
+{
+  obj_attr_subsection_v2 *subsec = elf_obj_attr_subsections (stdoutput).last;
+  /* If there is no current subsection, this function was called wrongly before
+     setting one (usually via the subsection directive).  */
+  gas_assert (subsec != NULL);
+
+  /* An attribute tag is an unsigned integer, so the expected token type should
+     always have the base type UNSIGNED_INTEGER.  Otherwise, this function was
+     called incorrectly.  */
+  gas_assert (token_type & UNSIGNED_INTEGER);
+
+  bool resolved = false;
+  const struct elf_backend_data *be = get_elf_backend_data (stdoutput);
+  const known_subsection_v2 *known_subsec
+    = identify_subsection (be, subsec->name);
+  if (known_subsec != NULL)
+    {
+      for (size_t i = 0; i < known_subsec->len && ! resolved; ++i)
+	resolved = resolve_if_matching (identifier,
+					&known_subsec->known_tags[i].tag,
+					val_out);
+    }
+
+  if (resolved)
+    /* An attribute tag is an unsigned integer, so the type of the found value
+       should be VALUE_UNSIGNED_INTEGER.  Otherwise, check if you set correctly
+       the type of the value associated to the symbol.  */
+    gas_assert (val_out->vtype == VALUE_UNSIGNED_INTEGER);
+
+  return resolved;
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+/* Look up known symbols, and try to resolve the given identifier.  */
+static bool
+lookup_known_symbols (const char *identifier,
+		      arg_token_t token_type,
+		      arg_t *val_out)
+{
+  if (identifier == NULL)
+    return false;
+
+  /* The identifier should match the value in val_out.  */
+  gas_assert (val_out->val.string == identifier);
+
+  arg_token_t high_ttype = (token_type & HT_MASK);
+
+#if (TC_OBJ_ATTR_v2)
+  static const gas_symbol_t known_identifiers_subsection_optional[] = {
+    { "optional", .value = { .val.b = true, .vtype = VALUE_BOOL } },
+    { "required", .value = { .val.b = false, .vtype = VALUE_BOOL } },
+  };
+
+  static const gas_symbol_t known_identifiers_subsection_encoding[] = {
+    { "ULEB128", .value = {
+	.val.u8 = obj_attr_encoding_v2_to_u8 (OA_ENC_ULEB128),
+	.vtype = VALUE_U8
+      }
+    },
+    { "uleb128", .value = {
+	.val.u8 = obj_attr_encoding_v2_to_u8 (OA_ENC_ULEB128),
+	.vtype = VALUE_U8
+      }
+    },
+    { "NTBS", .value =  {
+	.val.u8 = obj_attr_encoding_v2_to_u8 (OA_ENC_NTBS),
+	.vtype = VALUE_U8
+      }
+    },
+    { "ntbs", .value = {
+	.val.u8 = obj_attr_encoding_v2_to_u8 (OA_ENC_NTBS),
+	.vtype = VALUE_U8
+      }
+    },
+  };
+#endif /* TC_OBJ_ATTR_v2 */
+
+  bool resolved = false;
+
+#if (TC_OBJ_ATTR_v2)
+  if (high_ttype == SUBSECTION_OPTION_1 || high_ttype == SUBSECTION_OPTION_2)
+    {
+      const gas_symbol_t *known_identifiers
+	= (high_ttype == SUBSECTION_OPTION_1
+	   ? known_identifiers_subsection_optional
+	   : known_identifiers_subsection_encoding);
+      const size_t N_identifiers
+	= (high_ttype == SUBSECTION_OPTION_1
+	   ? ARRAY_SIZE (known_identifiers_subsection_optional)
+	   : ARRAY_SIZE (known_identifiers_subsection_encoding));
+
+      for (size_t i = 0; i < N_identifiers && ! resolved; ++i)
+	resolved = resolve_if_matching (identifier,
+					&known_identifiers[i],
+					val_out);
+    }
+  else
+#endif /* TC_OBJ_ATTR_v2 */
+  if (high_ttype == ATTRIBUTE_KEY)
+    {
+      obj_attr_version_t version = elf_obj_attr_version (stdoutput);
+#if (TC_OBJ_ATTR_v1)
+      if (version == OBJ_ATTR_V1)
+	resolved = obj_attr_v1_lookup_known_attr_key_symbol (identifier,
+	  token_type, val_out);
+#endif /* TC_OBJ_ATTR_v1 */
+#if (TC_OBJ_ATTR_v2)
+  #if (TC_OBJ_ATTR_v1)
+      else
+  #endif /* TC_OBJ_ATTR_v1 */
+      if (version == OBJ_ATTR_V2)
+	resolved = obj_attr_v2_lookup_known_attr_key_symbol (identifier,
+	  token_type, val_out);
+#endif /* TC_OBJ_ATTR_v2 */
+      else
+	abort ();
+    }
+  else
+    abort ();
+
+  return resolved;
+}
+
+/* Look up the symbol table of this compilation unit, and try to resolve the
+   given identifier.  */
+static bool
+lookup_symbol_table (const char *identifier,
+		     const arg_token_t expected_ttype,
+		     arg_t *val_out)
+{
+  if (identifier == NULL)
+    return false;
+
+  /* Note: signed integer are unsupported for now.  */
+  gas_assert (expected_ttype & UNSIGNED_INTEGER);
+  /* The identifier should match the value in val_out.  */
+  gas_assert (val_out->val.string == identifier);
+
+  symbolS *symbolP = symbol_find (identifier);
+  if (symbolP == NULL)
+    return false;
+
+  if (! S_IS_DEFINED (symbolP))
+    return false;
+
+  valueT val = S_GET_VALUE (symbolP);
+
+  /* Free the identifier since we found the value.  */
+  free ((void *) val_out->val.string);
+
+  val_out->val.u64 = val;
+  val_out->vtype = VALUE_UNSIGNED_INTEGER;
+
+  return true;
+}
+
+/* Return true if the next characters are suspected to represent an integer
+   literal.  */
+static bool
+look_like_integer_literal (char const* const cursor)
+{
+  return ISDIGIT (*cursor)
+    || ((*cursor == '+' || *cursor == '-') && ISDIGIT (*(cursor + 1)));
+}
+
+/* Parse an argument, and set its type accordingly depending on the input
+   value, and the constraints on the expected argument.  */
+static bool
+obj_attr_parse_arg (arg_token_t expected_ttype,
+		    bool (*match_identifier) (char c),
+		    bool resolve_identifier,
+		    arg_t *arg_out)
+{
+  const arg_token_t low_ttype = (expected_ttype & LT_MASK);
+
+  /* Note: symbol look-up for string literals is not available.  */
+  if (((low_ttype & STRING) && *input_line_pointer == '"')
+      || !(low_ttype & ~STRING))
+    return extract_string_literal (arg_out);
+
+  if (((low_ttype & (UNSIGNED_INTEGER | SIGNED_INTEGER))
+      && look_like_integer_literal (input_line_pointer))
+      || !(low_ttype & ~(UNSIGNED_INTEGER | SIGNED_INTEGER)))
+    return extract_integer_literal (arg_out, (low_ttype & UNSIGNED_INTEGER));
+
+  /* Try to extract an identifier.  */
+  if (! extract_identifier (match_identifier, arg_out))
+    return false;
+  gas_assert (arg_out->vtype == VALUE_STRING);
+
+  /* In some cases, we don't want to resolve the identifier because it is the
+     actual value.  */
+  if (! resolve_identifier)
+    return true;
+
+  /* The identifier is a symbol, let's try to resolve it by:
+     1. using the provided list of known symbols.
+       a) backend-independent
+       b) backend-specific.  */
+  if (lookup_known_symbols (arg_out->val.string, expected_ttype, arg_out))
+    return true;
+
+  /* 2. using the symbol table for this compilation unit.
+	Note: this is the last attempt before failure.  */
+  if (lookup_symbol_table (arg_out->val.string, low_ttype, arg_out))
+    return true;
+
+  as_bad ("unknown identifier '%s'", arg_out->val.string);
+  free ((void *) arg_out->val.string);
+  arg_out->val.string = NULL;
+  arg_out->vtype = VALUE_UNDEFINED;
+  return false;
+}
+
+/* Trim white spaces before a parameter.
+   Error if it meets a parameter separator before a parameter.  */
+static bool
+trim_whitespaces_before_param (int n)
+{
+  bool error = false;
+  do
+    {
+      skip_whitespace (input_line_pointer);
+      if (*input_line_pointer == ',')
+	{
+	  error = true;
+	  if (n < 0)
+	    as_bad (_("unexpected comma before value"));
+	  else
+	    as_bad (_("unexpected comma before parameter %d"), n + 1);
+	  skip_past_comma (&input_line_pointer);
+	}
+    }
+  while (is_whitespace (*input_line_pointer));
+  return !error;
+}
+
+/* Skip white spaces + parameter separator after a parameter.
+   Error if it does not meet a parameter separator after a parameter.  */
+static bool
+skip_whitespaces_past_comma (int n)
+{
+  skip_whitespace (input_line_pointer);
+  if (! skip_past_comma (&input_line_pointer))
+    {
+      if (n < 0)
+	as_bad (_("unexpected comma after value"));
+      else
+	as_bad (_("missing comma after parameter %d"), n + 1);
+      return false;
+    }
+  return true;
+}
+
+/* Can parse a list of arguments with variable length.  */
+static bool
+obj_attr_parse_args (arg_token_t expected_ttype,
+		     bool (*match_identifier) (char c),
+		     bool resolve_identifier,
+		     arg_t *arg_out)
+{
+  if ((expected_ttype & LIST) == 0)
+    return obj_attr_parse_arg (expected_ttype, match_identifier,
+      resolve_identifier, arg_out);
+
+  static const size_t LIST_MAX_SIZE = 2;
+  gas_variant_t *arg_list = xcalloc (LIST_MAX_SIZE, sizeof (gas_variant_t));
+
+  /* We don't want to support recursive lists.  */
+  expected_ttype &= ~LIST;
+
+  size_t n = 0;
+  do {
+    if (! trim_whitespaces_before_param (-1))
+      goto bad;
+
+    if (! obj_attr_parse_arg (expected_ttype, match_identifier,
+			      resolve_identifier, &arg_list[n]))
+      goto bad;
+
+    ++n;
+    skip_whitespace (input_line_pointer);
+    if (is_end_of_stmt (*input_line_pointer))
+      break;
+
+    if (! skip_whitespaces_past_comma (-1))
+      goto bad;
+
+    if (n >= LIST_MAX_SIZE)
+      {
+	as_bad ("too many arguments for a list (max: %lu)", LIST_MAX_SIZE);
+	goto bad;
+      }
+  } while (n < LIST_MAX_SIZE);
+
+  arg_out->vtype = VALUE_LIST;
+  arg_out->val.list.len = n;
+  arg_out->val.list.elts = arg_list;
+  return true;
+
+ bad:
+  args_list_free (arg_list, n);
+  return false;
+}
+
+#if (TC_OBJ_ATTR_v2)
+static bool
+is_valid_boolean (uint64_t value)
+{
+  return value == 0 || value == 1;
+}
+
+#define is_valid_optional is_valid_boolean
+
+static bool
+is_valid_encoding (uint64_t value)
+{
+  value = obj_attr_encoding_v2_from_u8 (value);
+  return OA_ENC_UNSET < value && value <= OA_ENC_MAX;
+}
+
+static bool
+match_subsection_identifier (char c)
+{
+  return ISALNUM (c) || c == '_' || c == '-';
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+static bool
+match_symbol (char c)
+{
+  return ISALNUM (c) || c == '_';
+}
+
+#define match_tag_identifier match_symbol
+
+#if (TC_OBJ_ATTR_v1)
+/* Determine the expected argument type based on the tag ID.  */
+static arg_token_t
+obj_attr_v1_get_arg_type (bfd *abfd,
+			  obj_attr_vendor_t vendor,
+			  obj_attr_tag_t tag)
+{
+  int attr_type = _bfd_elf_obj_attrs_arg_type (abfd, vendor, tag);
+  arg_token_t arg_type;
+  if (attr_type == (ATTR_TYPE_FLAG_STR_VAL | ATTR_TYPE_FLAG_INT_VAL))
+    arg_type = LIST | UNSIGNED_INTEGER | STRING;
+  else if (attr_type == ATTR_TYPE_FLAG_STR_VAL)
+    arg_type = STRING;
+  else
+    /* Covers the remaning cases:
+       - ATTR_TYPE_FLAG_INT_VAL.
+       - ATTR_TYPE_FLAG_INT_VAL | ATTR_TYPE_FLAG_NO_DEFAULT.  */
+    arg_type = UNSIGNED_INTEGER;
+  return arg_type;
+}
+#endif /* TC_OBJ_ATTR_v1 */
+
+#if (TC_OBJ_ATTR_v2)
+/* Determine the expected argument type based on the subsection encoding.  */
+static arg_token_t
+obj_attr_v2_get_arg_type (obj_attr_encoding_v2 subsec_encoding)
+{
+  arg_token_t arg_type;
+  switch (subsec_encoding)
+    {
+    case OA_ENC_ULEB128:
+      arg_type = UNSIGNED_INTEGER;
+      break;
+    case OA_ENC_NTBS:
+      arg_type = STRING;
+      break;
+    case OA_ENC_UNSET:
+    default:
+      abort ();
+    }
+  return arg_type;
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+/* Parse the arguments of [vendor]_attribute directive.  */
+static arg_t *
+vendor_attribute_parse_args (
+#if (TC_OBJ_ATTR_v1 && TC_OBJ_ATTR_v2)
+			     obj_attr_vendor_t vendor,
+			     const obj_attr_subsection_v2 *subsec,
+#elif (TC_OBJ_ATTR_v1)
+			     obj_attr_vendor_t vendor,
+			     const obj_attr_subsection_v2 *subsec ATTRIBUTE_UNUSED,
+#else /* TC_OBJ_ATTR_v2 */
+			     obj_attr_vendor_t vendor ATTRIBUTE_UNUSED,
+			     const obj_attr_subsection_v2 *subsec,
+#endif
+			     unsigned int nargs, ...)
+{
+  va_list args;
+  va_start (args, nargs);
+
+  arg_t *args_out = xcalloc (nargs, sizeof (arg_t));
+
+  for (unsigned int n = 0; n < nargs; ++n)
+    {
+      if (! trim_whitespaces_before_param (n))
+	goto bad;
+
+      arg_t *arg_out = &args_out[n];
+
+      arg_token_t expected_ttype = va_arg (args, arg_token_t);
+      arg_token_t high_ttype = (expected_ttype & HT_MASK);
+      /* Make sure that we called the right parse_args().  */
+      gas_assert (high_ttype == ATTRIBUTE_KEY
+	       || high_ttype == ATTRIBUTE_VALUE);
+
+      if (high_ttype == ATTRIBUTE_VALUE)
+	{
+	  arg_token_t type_attr_value
+#if (TC_OBJ_ATTR_v1 && TC_OBJ_ATTR_v2)
+	    = (subsec != NULL)
+	    ? obj_attr_v2_get_arg_type (subsec->encoding)
+	    : obj_attr_v1_get_arg_type (stdoutput, vendor,
+					args_out[n-1].val.u32);
+#elif (TC_OBJ_ATTR_v1)
+	    = obj_attr_v1_get_arg_type (stdoutput, vendor,
+					args_out[n-1].val.u32);
+#else /* TC_OBJ_ATTR_v2 */
+	    = obj_attr_v2_get_arg_type (subsec->encoding);
+#endif
+	  expected_ttype |= type_attr_value;
+	}
+
+      if (! obj_attr_parse_args (expected_ttype,
+				 match_tag_identifier, true,
+				 arg_out))
+	{
+	  if (high_ttype == ATTRIBUTE_KEY)
+	    as_bad (_("could not parse attribute tag"));
+	  else
+	    as_bad (_("could not parse attribute value"));
+	  goto bad;
+	}
+
+      if (n + 1 < nargs && !skip_whitespaces_past_comma (n))
+	goto bad;
+    }
+
+  va_end (args);
+  demand_empty_rest_of_line ();
+
+  return args_out;
+
+bad:
+  args_list_free (args_out, nargs);
+  va_end (args);
+  ignore_rest_of_line ();
+  return NULL;
+}
+
+#if (TC_OBJ_ATTR_v1)
+/* Record an attribute (object attribute v1 only).  */
+static obj_attribute *
+obj_attr_v1_record (bfd *abfd,
+		    const obj_attr_vendor_t vendor,
+		    const obj_attr_tag_t tag,
+		    arg_t *parsed_arg)
+{
+  obj_attribute *attr = elf_new_obj_attr (abfd, vendor, tag);
+  if (attr != NULL)
+    {
+      int tag_type = _bfd_elf_obj_attrs_arg_type (abfd, vendor, tag);
+      if (parsed_arg->vtype == VALUE_LIST)
+	{
+	  gas_assert (parsed_arg->val.list.len == 2
+	    && parsed_arg->val.list.elts[0].vtype == VALUE_UNSIGNED_INTEGER
+	    && parsed_arg->val.list.elts[1].vtype == VALUE_STRING);
+	  attr->type = tag_type;
+	  attr->i = parsed_arg->val.list.elts[0].val.u64;
+	  attr->s = (char *) parsed_arg->val.list.elts[1].val.string;
+	  parsed_arg->val.list.elts[1].val.string = NULL;
+	}
+      else if (parsed_arg->vtype == VALUE_STRING)
+	{
+	  attr->type = tag_type;
+	  attr->s = (char *) parsed_arg->val.string;
+	  parsed_arg->val.string = NULL;
+	}
+      else
+	{
+	  attr->type = tag_type;
+	  attr->i = parsed_arg->val.u64;
+	}
+    }
+  return attr;
+}
+#endif /* TC_OBJ_ATTR_v1 */
+
+#if (TC_OBJ_ATTR_v2)
+/* Parse the arguments of [vendor]_subsection directive (v2 only).  */
+static arg_t *
+vendor_subsection_parse_args (unsigned int nargs, ...)
+{
+  va_list args;
+  va_start (args, nargs);
+
+  arg_t *args_out = xcalloc (nargs, sizeof (arg_t));
+
+  for (unsigned int n = 0; n < nargs; ++n)
+    {
+      if (! trim_whitespaces_before_param (n))
+	goto bad;
+
+      arg_t *arg_out = &args_out[n];
+
+      arg_token_t expected_ttype = va_arg (args, arg_token_t);
+      arg_token_t high_ttype = (expected_ttype & HT_MASK);
+      /* Make sure that we called the right parse_args().  */
+      gas_assert (high_ttype == SUBSECTION_NAME
+	       || high_ttype == SUBSECTION_OPTION_1
+	       || high_ttype == SUBSECTION_OPTION_2);
+
+      if (high_ttype == SUBSECTION_NAME)
+	{
+	  if ( !obj_attr_parse_arg (expected_ttype,
+				    match_subsection_identifier, false,
+				    arg_out))
+	    {
+	      as_bad (_("expected <subsection_name>, <comprehension>, "
+			"<encoding>"));
+	      goto bad;
+	    }
+	}
+      else if (high_ttype == SUBSECTION_OPTION_1
+	    || high_ttype == SUBSECTION_OPTION_2)
+	{
+	  if (! obj_attr_parse_arg (expected_ttype,
+				    match_symbol, true,
+				    arg_out))
+	    goto bad;
+	  if (high_ttype == SUBSECTION_OPTION_1
+	      && ! is_valid_optional (arg_out->val.u64))
+	    {
+	      as_bad (("invalid value %lu, expected values for <comprehension> "
+		       "are 0 (=required) or 1 (=optional)"), arg_out->val.u64);
+	      goto bad;
+	    }
+	  else if (high_ttype == SUBSECTION_OPTION_2
+		&& ! is_valid_encoding (arg_out->val.u64))
+	    {
+	      as_bad (("invalid value %lu, expected values for <encoding> are 0"
+		       " (=ULEB128) or 1 (=NTBS)"), arg_out->val.u64);
+	      goto bad;
+	    }
+	}
+      else
+	abort ();
+
+      if (n + 1 < nargs && ! skip_whitespaces_past_comma (n))
+	goto bad;
+    }
+
+  va_end (args);
+  demand_empty_rest_of_line ();
+
+  return args_out;
+
+bad:
+  args_list_free (args_out, nargs);
+  va_end (args);
+  ignore_rest_of_line ();
+  return NULL;
+}
+
+/* Record an attribute (object attribute v2 only).  */
+static void
+obj_attr_v2_record (uint64_t key, arg_t *arg_val)
+{
+  /* An OAv2 cannot be recorded unless a subsection has been recorded.  */
+  gas_assert (elf_obj_attr_subsections (stdoutput).last != NULL);
+
+  union obj_attr_value_v2 obj_attr_vals;
+  if (arg_val->vtype == VALUE_UNSIGNED_INTEGER)
+    obj_attr_vals.uint_val = arg_val->val.u64;
+  else
+    {
+      /* Move the string.  */
+      obj_attr_vals.string_val = arg_val->val.string;
+      arg_val->val.string = NULL;
+    }
+
+  obj_attr_v2 *obj_attr = _bfd_elf_obj_attr_v2_init (key, obj_attr_vals);
+  gas_assert (obj_attr != NULL);
+
+  /* Go over the list of already recorded attributes and check for
+     redefinitions (which are forbidden).  */
+  bool skip_recording = false;
+  obj_attr_v2 *recorded_attr = obj_attr_v2_find_by_tag
+    (elf_obj_attr_subsections (stdoutput).last, obj_attr->tag, false);
+  if (recorded_attr != NULL)
+    {
+      if ((arg_val->vtype == VALUE_UNSIGNED_INTEGER
+	   && recorded_attr->vals.uint_val != obj_attr->vals.uint_val)
+	  || (arg_val->vtype == VALUE_STRING
+	      && strcmp (recorded_attr->vals.string_val,
+			 obj_attr->vals.string_val) != 0))
+	as_bad (_("attribute %u cannot be redefined"), recorded_attr->tag);
+      skip_recording = true;
+    }
+
+  if (skip_recording)
+    {
+      if (arg_val->vtype == VALUE_STRING)
+	free ((void *) obj_attr->vals.string_val);
+      free (obj_attr);
+      return;
+    }
+
+  LINKED_LIST_APPEND(obj_attr_v2) (
+    elf_obj_attr_subsections (stdoutput).last, obj_attr);
+}
+
+/* Record a subsection (object attribute v2 only).  */
+static void
+obj_attr_v2_subsection_record (const char *name,
+			       bool optional,
+			       obj_attr_encoding_v2 encoding)
+{
+  obj_attr_subsection_v2 *already_recorded_subsec
+    = obj_attr_subsection_v2_find_by_name
+      (elf_obj_attr_subsections (stdoutput).first, name, false);
+
+  if (already_recorded_subsec != NULL)
+    {
+      /* Check for mismatching redefinition of the subsection, i.e. the names
+	 match but the properties are different.  */
+      if ((already_recorded_subsec->optional != optional)
+       || (already_recorded_subsec->encoding != encoding))
+	{
+	  const char *prev_comprehension = oav2_comprehension_to_string (
+	    already_recorded_subsec->optional);
+	  const char *prev_encoding = oav2_encoding_to_string (
+	    already_recorded_subsec->encoding);
+	  as_bad (_("incompatible redeclaration of subsection %s.  Previous "
+		    "declaration had properties: comprehension=%s, encoding"
+		    "=%s."), name, prev_comprehension, prev_encoding);
+	  return;
+	}
+      /* Move the existing subsection to the last position.  */
+      LINKED_LIST_REMOVE(obj_attr_subsection_v2)
+	(&elf_obj_attr_subsections (stdoutput), already_recorded_subsec);
+      LINKED_LIST_APPEND(obj_attr_subsection_v2)
+	(&elf_obj_attr_subsections (stdoutput), already_recorded_subsec);
+    }
+  else
+    {
+      const char *vendor_name
+	= get_elf_backend_data (stdoutput)->obj_attrs_vendor;
+      obj_attr_subsection_scope_v2 scope
+	= (strncmp (name, vendor_name, strlen (vendor_name)) == 0
+	   ? OA_SUBSEC_PUBLIC
+	   : OA_SUBSEC_PRIVATE);
+
+      obj_attr_subsection_v2 *new_subsection
+	= _bfd_elf_obj_attr_subsection_v2_init (name, scope, optional, encoding);
+      LINKED_LIST_APPEND(obj_attr_subsection_v2)
+	(&elf_obj_attr_subsections (stdoutput), new_subsection);
+    }
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+/* Parse an attribute directive (supports both v1 & v2).  */
+obj_attr_tag_t
+obj_attr_process_attribute (obj_attr_vendor_t vendor)
+{
+  obj_attr_version_t version = elf_obj_attr_version (stdoutput);
+
+  obj_attr_subsection_v2 *subsec = NULL;
+#if (TC_OBJ_ATTR_v2)
+  if (version == OBJ_ATTR_V2)
+    {
+      subsec = elf_obj_attr_subsections (stdoutput).last;
+      if (subsec == NULL)
+	{
+	  as_bad (_("declaration of an attribute outside the scope of an "
+		    "attribute subsection"));
+	  ignore_rest_of_line ();
+	  return 0;
+	}
+    }
+#endif /* TC_OBJ_ATTR_v2 */
+
+  const size_t N_ARGS = 2;
+  arg_t *args = vendor_attribute_parse_args (
+    vendor, subsec, N_ARGS,
+    ATTRIBUTE_KEY | IDENTIFIER | UNSIGNED_INTEGER,
+    ATTRIBUTE_VALUE);
+
+  if (args == NULL)
+    return 0;
+
+  obj_attr_tag_t tag = args[0].val.u64;
+#if (TC_OBJ_ATTR_v1)
+  if (version == OBJ_ATTR_V1)
+    {
+      oav1_attr_record_seen (vendor, tag);
+      obj_attr_v1_record (stdoutput, vendor, tag, &args[1]);
+    }
+#endif /* TC_OBJ_ATTR_v1 */
+#if (TC_OBJ_ATTR_v2)
+  #if (TC_OBJ_ATTR_v1)
+  else
+  #endif /* TC_OBJ_ATTR_v1 */
+  if (version == OBJ_ATTR_V2)
+    obj_attr_v2_record (tag, &args[1]);
+#endif /* TC_OBJ_ATTR_v2 */
+  else
+    abort ();
+
+  args_list_free (args, N_ARGS);
+
+  return tag;
+}
+
+#if (TC_OBJ_ATTR_v2)
+/* Parse an object attribute v2's subsection directive.  */
+void
+obj_attr_process_subsection ()
+{
+  const size_t N_ARGS = 3;
+  arg_t *args = vendor_subsection_parse_args (
+    N_ARGS,
+    SUBSECTION_NAME | IDENTIFIER,
+    SUBSECTION_OPTION_1 | IDENTIFIER | UNSIGNED_INTEGER,
+    SUBSECTION_OPTION_2 | IDENTIFIER | UNSIGNED_INTEGER);
+
+  if (args == NULL)
+    return;
+
+  /* move the value to avoid double free.  */
+  const char *name = args[0].val.string;
+  args[0].val.string = NULL;
+
+  obj_attr_v2_subsection_record (name, args[1].val.u64,
+    obj_attr_encoding_v2_from_u8 (args[2].val.u64));
+
+  args_list_free (args, N_ARGS);
+}
+#endif /* TC_OBJ_ATTR_v2 */
+
+#if (TC_OBJ_ATTR_v1)
 /* Parse an attribute directive for VENDOR.
    Returns the attribute number read, or zero on error.  */
 
@@ -233,5 +1181,6 @@ obj_attr_v1_process_attribute (obj_attr_vendor_t vendor)
   ignore_rest_of_line ();
   return 0;
 }
+#endif /* TC_OBJ_ATTR_v1 */
 
 #endif /* TC_OBJ_ATTR */
diff --git a/gas/config/obj-elf-attr.h b/gas/config/obj-elf-attr.h
index bc227c513ff..0145e69823e 100644
--- a/gas/config/obj-elf-attr.h
+++ b/gas/config/obj-elf-attr.h
@@ -23,12 +23,36 @@
 
 #include "as.h"
 
+#ifndef TC_OBJ_ATTR_V1
+#define TC_OBJ_ATTR_V1 0
+#endif
+#ifndef TC_OBJ_ATTR_V2
+#define TC_OBJ_ATTR_V2 0
+#endif
+
+#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
+  #define TC_OBJ_ATTR 1
+#endif
+
 #ifdef TC_OBJ_ATTR
 
+#if (TC_OBJ_ATTR_v1)
 extern void oav1_attr_info_init (void);
 extern void oav1_attr_info_exit (void);
 extern bool oav1_attr_seen (obj_attr_vendor_t, obj_attr_tag_t);
+#endif /* TC_OBJ_ATTR_v1 */
+
+/* Object attributes parsers.  */
+
+#if (TC_OBJ_ATTR_v1)
 extern obj_attr_tag_t obj_attr_v1_process_attribute (obj_attr_vendor_t);
+#endif /* (TC_OBJ_ATTR_v1) */
+#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
+extern obj_attr_tag_t obj_attr_process_attribute (obj_attr_vendor_t);
+#endif /* (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2) */
+#if (TC_OBJ_ATTR_v2)
+extern void obj_attr_process_subsection (void);
+#endif /* (TC_OBJ_ATTR_v2) */
 
 #endif /* TC_OBJ_ATTR */
 
diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c
index e4d76c903bb..ed534ce5378 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -72,7 +72,9 @@ static void obj_elf_symver (int);
 static void obj_elf_subsection (int);
 static void obj_elf_popsection (int);
 #ifdef TC_OBJ_ATTR
+#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
 static void obj_elf_gnu_attribute (int);
+#endif /* (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2) */
 #endif /* TC_OBJ_ATTR */
 static void obj_elf_tls_common (int);
 static void obj_elf_lcomm (int);
@@ -119,7 +121,9 @@ static const pseudo_typeS elf_pseudo_table[] =
 
   /* A GNU extension for object attributes.  */
 #ifdef TC_OBJ_ATTR
+#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
   {"gnu_attribute", obj_elf_gnu_attribute, 0},
+#endif /* (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2) */
 #endif /* TC_OBJ_ATTR */
 
   /* These are used for dwarf2.  */
@@ -2056,13 +2060,20 @@ obj_elf_vtable_entry (int ignore ATTRIBUTE_UNUSED)
 
 #ifdef TC_OBJ_ATTR
 
+#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
 /* Parse a .gnu_attribute directive.  */
 
 static void
 obj_elf_gnu_attribute (int ignored ATTRIBUTE_UNUSED)
 {
+#if (TC_OBJ_ATTR_v1)
   obj_attr_v1_process_attribute (OBJ_ATTR_GNU);
+#endif /* TC_OBJ_ATTR_v1 */
+#if (TC_OBJ_ATTR_v2)
+  obj_attr_process_attribute (OBJ_ATTR_GNU);
+#endif /* TC_OBJ_ATTR_v2 */
 }
+#endif /* (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2) */
 
 #endif /* TC_OBJ_ATTR */
 
@@ -3003,7 +3014,9 @@ elf_begin (void)
   elf_obj_attr_version (stdoutput)
     = get_elf_backend_data (stdoutput)->default_obj_attr_version;
 
+#if TC_OBJ_ATTR_v1
   oav1_attr_info_init ();
+#endif /* TC_OBJ_ATTR_v1 */
 #endif /* TC_OBJ_ATTR */
 }
 
@@ -3022,9 +3035,9 @@ elf_end (void)
       free (groups.head);
     }
 
-#ifdef TC_OBJ_ATTR
+#if defined (TC_OBJ_ATTR) && TC_OBJ_ATTR_v1
   oav1_attr_info_exit ();
-#endif /* TC_OBJ_ATTR */
+#endif /* defined (TC_OBJ_ATTR) && TC_OBJ_ATTR_v1 */
 }
 
 #ifdef USE_EMULATIONS
diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
index cd9ae581b9a..e7ad9c544fb 100644
--- a/gas/config/tc-aarch64.c
+++ b/gas/config/tc-aarch64.c
@@ -2409,6 +2409,20 @@ s_tlsdescldr (int ignored ATTRIBUTE_UNUSED)
 
   demand_empty_rest_of_line ();
 }
+
+/* Parse a .aeabi_subsection directive.  */
+static void
+s_aarch64_aeabi_subsection (int ignored ATTRIBUTE_UNUSED)
+{
+  obj_attr_process_subsection ();
+}
+
+/* Parse a .aeabi_attribute directive.  */
+static void
+s_aarch64_aeabi_attribute (int ignored ATTRIBUTE_UNUSED)
+{
+  obj_attr_process_attribute (OBJ_ATTR_PROC);
+}
 #endif	/* OBJ_ELF */
 
 #ifdef TE_PE
@@ -2490,6 +2504,8 @@ const pseudo_typeS md_pseudo_table[] = {
   {"tlsdesccall", s_tlsdesccall, 0},
   {"tlsdescldr", s_tlsdescldr, 0},
   {"variant_pcs", s_variant_pcs, 0},
+  {"aeabi_subsection", s_aarch64_aeabi_subsection, 0},
+  {"aeabi_attribute", s_aarch64_aeabi_attribute, 0},
 #endif
 #if defined(OBJ_ELF) || defined(OBJ_COFF)
   {"word", s_aarch64_cons, 4},
diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
index fce34ed48d1..dd88ac72a99 100644
--- a/gas/config/tc-aarch64.h
+++ b/gas/config/tc-aarch64.h
@@ -370,4 +370,9 @@ void tc_pe_dwarf2_emit_offset (symbolS *, unsigned int);
 
 #endif /* TE_PE */
 
+#ifdef OBJ_ELF
+/* The target supports Object Attributes v2.  */
+#define TC_OBJ_ATTR_v2 1
+#endif /* OBJ_ELF */
+
 #endif /* TC_AARCH64 */
diff --git a/gas/config/tc-arc.h b/gas/config/tc-arc.h
index 4e2ada36594..082a25f0de0 100644
--- a/gas/config/tc-arc.h
+++ b/gas/config/tc-arc.h
@@ -271,7 +271,7 @@ struct arc_relax_type
 extern void arc_md_end (void);
 #define md_end arc_md_end
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif
diff --git a/gas/config/tc-arm.h b/gas/config/tc-arm.h
index 1f1301a7a91..0f60da2a881 100644
--- a/gas/config/tc-arm.h
+++ b/gas/config/tc-arm.h
@@ -379,6 +379,6 @@ extern bool arm_tc_equal_in_insn (int, char *);
 int arm_is_largest_exponent_ok (int precision);
 
 #ifdef OBJ_ELF
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
-#endif
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
+#endif /* OBJ_ELF */
diff --git a/gas/config/tc-csky.h b/gas/config/tc-csky.h
index a86e0153e26..f3c8f744b80 100644
--- a/gas/config/tc-csky.h
+++ b/gas/config/tc-csky.h
@@ -106,7 +106,7 @@ extern long csky_relax_frag (segT, fragS *, long);
 const char * elf32_csky_target_format (void);
 #endif
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif
diff --git a/gas/config/tc-m68k.h b/gas/config/tc-m68k.h
index 7ab701489f3..91e435cab13 100644
--- a/gas/config/tc-m68k.h
+++ b/gas/config/tc-m68k.h
@@ -155,7 +155,7 @@ struct m68k_tc_sy
 
 #define TC_SYMFIELD_TYPE struct m68k_tc_sy
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif /* TC_M68K_H */
diff --git a/gas/config/tc-mips.h b/gas/config/tc-mips.h
index 423229a7e25..4b9f1eee0aa 100644
--- a/gas/config/tc-mips.h
+++ b/gas/config/tc-mips.h
@@ -217,7 +217,7 @@ extern bfd_reloc_code_real_type mips_cfi_reloc_for_encoding (int encoding);
 #define CONVERT_SYMBOLIC_ATTRIBUTE(name) mips_convert_symbolic_attribute (name)
 extern int mips_convert_symbolic_attribute (const char *);
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif /* TC_MIPS */
diff --git a/gas/config/tc-msp430.h b/gas/config/tc-msp430.h
index 01b09041d4f..bb6e50eaf8b 100644
--- a/gas/config/tc-msp430.h
+++ b/gas/config/tc-msp430.h
@@ -173,5 +173,5 @@ extern bool msp430_allow_local_subtract (expressionS *, expressionS *, segT);
 
 #define DWARF2_ADDR_SIZE(bfd) 4
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
diff --git a/gas/config/tc-ppc.h b/gas/config/tc-ppc.h
index 5041b3f8708..12970ed60d1 100644
--- a/gas/config/tc-ppc.h
+++ b/gas/config/tc-ppc.h
@@ -369,6 +369,6 @@ extern int ppc_dwarf2_line_min_insn_length;
 #define EH_FRAME_ALIGNMENT		2
 
 #ifdef OBJ_ELF
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
-#endif
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
+#endif /* OBJ_ELF */
diff --git a/gas/config/tc-riscv.h b/gas/config/tc-riscv.h
index 2348ec33640..612d2b65254 100644
--- a/gas/config/tc-riscv.h
+++ b/gas/config/tc-riscv.h
@@ -187,7 +187,7 @@ extern void riscv_adjust_symtab (void);
 #define OBJ_COPY_SYMBOL_ATTRIBUTES(DEST, SRC)  \
   elf_copy_symbol_size (DEST, SRC)
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif /* TC_RISCV */
diff --git a/gas/config/tc-s390.h b/gas/config/tc-s390.h
index 7031560446e..1afef5ef266 100644
--- a/gas/config/tc-s390.h
+++ b/gas/config/tc-s390.h
@@ -130,5 +130,5 @@ extern offsetT s390_sframe_cfa_ra_offset (void);
 unsigned char s390_sframe_get_abi_arch (void);
 #define sframe_get_abi_arch s390_sframe_get_abi_arch
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
diff --git a/gas/config/tc-sparc.h b/gas/config/tc-sparc.h
index c0853d8d250..d819451a572 100644
--- a/gas/config/tc-sparc.h
+++ b/gas/config/tc-sparc.h
@@ -178,7 +178,7 @@ extern int sparc_cie_data_alignment;
    this, BFD_RELOC_32_PCREL will be emitted directly instead.  */
 #define CFI_DIFF_EXPR_OK 0
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
 
 #endif
diff --git a/gas/config/tc-tic6x.h b/gas/config/tc-tic6x.h
index afa7a874254..c3f49b120dc 100644
--- a/gas/config/tc-tic6x.h
+++ b/gas/config/tc-tic6x.h
@@ -226,5 +226,5 @@ void tic6x_cfi_endproc (struct fde_entry *fde);
 
 #define tc_cfi_section_name ".c6xabi.exidx"
 
-/* The target supports Object Attributes.  */
-#define TC_OBJ_ATTR 1
+/* The target supports Object Attributes v1.  */
+#define TC_OBJ_ATTR_v1 1
diff --git a/gas/configure b/gas/configure
index ec5ba8c08f5..244cf042fca 100755
--- a/gas/configure
+++ b/gas/configure
@@ -12351,7 +12351,7 @@ _ACEOF
 
     # Does the target support Object Attributes ?
     case ${cpu_type} in
-      arc* | arm* | csky | m68k | mips* | msp430 | powerpc* \
+      aarch64* | arc* | arm* | csky | m68k | mips* | msp430 | powerpc* \
       | riscv* | s390* | sparc* | tic6x)
 	for f in config/obj-elf-attr.o; do
 	  case " $extra_objects " in
diff --git a/gas/configure.ac b/gas/configure.ac
index c0116c7b79a..a32c14cfd29 100644
--- a/gas/configure.ac
+++ b/gas/configure.ac
@@ -456,7 +456,7 @@ changequote([,])dnl
 
     # Does the target support Object Attributes ?
     case ${cpu_type} in
-      arc* | arm* | csky | m68k | mips* | msp430 | powerpc* \
+      aarch64* | arc* | arm* | csky | m68k | mips* | msp430 | powerpc* \
       | riscv* | s390* | sparc* | tic6x)
 	for f in config/obj-elf-attr.o; do
 	  case " $extra_objects " in
diff --git a/gas/doc/c-aarch64.texi b/gas/doc/c-aarch64.texi
index dc64e09e766..b5b26d83b64 100644
--- a/gas/doc/c-aarch64.texi
+++ b/gas/doc/c-aarch64.texi
@@ -479,6 +479,33 @@ The AArch64 architecture uses @sc{ieee} floating-point numbers.
 
 @c AAAAAAAAAAAAAAAAAAAAAAAAA
 
+@cindex @code{.aeabi_subsection} directive, AArch64
+@item .aeabi_subsection @var{name}, @var{comprehension}, @var{encoding}
+Create or switch the current object attributes subsection to @var{name}.  Valid
+values for @var{name} are following the pattern @code{[a-zA-Z0-9_-]+}.
+
+The subsection property @var{comprehension} determines how a program processing
+the attributes handles attributes that it does not recognize (perhaps because
+the object file was generated by a different version of the toolchain).  A
+subsection that is marked @code{optional} can be skipped if it is not
+understood.  A subsection marked @code{required} implies that information
+conveyed by the attribute is required for correct processing of the object file;
+a fatal diagnostic must be generated if a tool does not recognize either the tag
+or the value associated with it.
+
+@var{encoding} specifies the expected encoding of the attributes recorded in the
+subsection.  Currently supported values are @code{ULEB128} and @code{NTBS}
+(null-terminated byte string).
+
+@cindex @code{.aeabi_attribute} @var{tag}, @var{value}
+@item .aeabi_attribute @var{tag}, @var{value}
+Create an attribute with the pair @var{tag}, @var{value} in the current
+subsection.  @var{tag} can either be an integer value, or a known named key.
+@var{value} can either be an integer or a string.
+
+The complete list of subsections and tags supported on AArch64 is documented
+in @cite{Build Attributes for the Arm 64-bit Architecture (AArch64)}.
+
 @cindex @code{.arch} directive, AArch64
 @item .arch @var{name}
 Select the target architecture.  Valid values for @var{name} are the same as
diff --git a/include/elf/aarch64.h b/include/elf/aarch64.h
index 74bd7b402e0..be3ba6db381 100644
--- a/include/elf/aarch64.h
+++ b/include/elf/aarch64.h
@@ -59,6 +59,19 @@
 #define STO_AARCH64_VARIANT_PCS	0x80  /* Symbol may follow different call
 					 convention from the base PCS.  */
 
+/* Tags used in aeabi_feature_and_bits subsection.  */
+typedef enum Tag_Feature_XXX {
+  Tag_Feature_BTI = 0,
+  Tag_Feature_PAC = 1,
+  Tag_Feature_GCS = 2,
+} Tag_Feature_XXX;
+
+/* Tags used in aeabi_pauthabi subsection.  */
+typedef enum Tag_PAuth_XXX {
+  Tag_PAuth_Platform = 1,
+  Tag_PAuth_Schema = 2,
+} Tag_PAuth_XXX;
+
 /* Relocation types.  */
 
 START_RELOC_NUMBERS (elf_aarch64_reloc_type)
-- 
2.50.1



More information about the Binutils mailing list