[PATCH v5 05/20] gas: implement parsing of object attributes v2

Richard Earnshaw (lists) Richard.Earnshaw@arm.com
Wed Jul 9 13:55:08 GMT 2025


On 07/07/2025 17:49, Matthieu Longo wrote:
> 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           | 132 +++++-
>  bfd/elf-attrs.h           |  71 +++
>  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 | 941 ++++++++++++++++++++++++++++++++++++++
>  gas/config/obj-elf-attr.h |  37 +-
>  gas/config/obj-elf.c      |  17 +-
>  gas/config/tc-aarch64.c   |  16 +
>  gas/doc/c-aarch64.texi    |  23 +
>  include/elf/aarch64.h     |  13 +
>  13 files changed, 1339 insertions(+), 7 deletions(-)
> 
> diff --git a/bfd/elf-attrs.c b/bfd/elf-attrs.c
> index 1ccbcc40d4d..acf93a3cee5 100644
> --- a/bfd/elf-attrs.c
> +++ b/bfd/elf-attrs.c
> @@ -255,8 +255,138 @@ 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;

What's the reason for using strncmp here rather than strcmp?

> +}
> +
> +/* 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;
> +}
> +
>  /* 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 22be10d2bf5..9105dcaaf72 100644
> --- a/bfd/elf-attrs.h
> +++ b/bfd/elf-attrs.h
> @@ -116,3 +116,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);

Indentation should be to the level of the opening parenthesis.

> +
> +extern const char *
> +obj_attr_v2_tag_to_string (const struct elf_backend_data *, const char*,
> +  obj_attr_tag_t);

and here.

> diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
> index 2e617964300..c17540c819d 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
> @@ -3093,6 +3099,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 96ca6e13338..4ef18a052cd 100644
> --- a/bfd/elfnn-aarch64.c
> +++ b/bfd/elfnn-aarch64.c
> @@ -10782,6 +10782,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..2d738bbe47a 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 a98eef1e66b..92b60439f6d 100644
> --- a/bfd/elfxx-aarch64.h
> +++ b/bfd/elfxx-aarch64.h
> @@ -215,6 +215,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 738fd512169..9277edf8690 100644
> --- a/bfd/elfxx-target.h
> +++ b/bfd/elfxx-target.h
> @@ -565,6 +565,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
> @@ -946,6 +952,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 4bfbf973ea5..de5fcbfc191 100644
> --- a/gas/config/obj-elf-attr.c
> +++ b/gas/config/obj-elf-attr.c
> @@ -21,6 +21,7 @@
>  #include "obj-elf-attr.h"
>  
>  #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.  */
> @@ -122,6 +125,943 @@ 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>, <optional>, <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 <optional> 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))
> +	{
> +	  as_bad (_("recalled subsections must have the same parameters"));
> +	  return;

Even with the source code in front of me, I'm not entirely sure what 'recalled' means in this context.  Perhaps a clearer error would be something along the lines of

"Incompatible redeclaration of subsection <name>.  Previous declaration had parameters: <list>" 

where 'name' and 'list' refer back to the subsection and properties of the subsection we need to match.


> +	}
> +      /* 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;
> +
> +  const char *name = NULL;
> +  /* move the value to avoid double free.  */
> +  VALUE_SWAP (name, args[0].val.string);
> +
> +  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.  */
>  
> @@ -235,5 +1175,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 e7631b8b05c..0f95fa44be4 100644
> --- a/gas/config/obj-elf-attr.h
> +++ b/gas/config/obj-elf-attr.h
> @@ -24,9 +24,10 @@
>  #include "as.h"
>  #include "bfd/elf-bfd.h"
>  
> +#if OBJ_ELF
> +
>  /* The target supports Object Attributes v1.  */
> -#if OBJ_ELF \
> - && (defined (TC_ARC) \
> +#if defined (TC_ARC) \
>    || defined (TC_ARM) \
>    || defined (TC_CSKY) \
>    || defined (TC_M68K) \
> @@ -36,16 +37,44 @@
>    || defined (TC_RISCV) \
>    || defined (TC_S390) \
>    || defined (TC_SPARC) \
> -  || defined (TC_TIC6X))
> -#define TC_OBJ_ATTR 1
> +  || defined (TC_TIC6X)
> +#define TC_OBJ_ATTR_v1 1
> +#else
> +#define TC_OBJ_ATTR_v1 0
> +#endif
> +
> +/* The target supports Object Attributes v2.  */
> +#if defined(TC_AARCH64)
> +#define TC_OBJ_ATTR_v2 1
> +#else
> +#define TC_OBJ_ATTR_v2 0
> +#endif
> +
> +#if (TC_OBJ_ATTR_v1 || TC_OBJ_ATTR_v2)
> +  #define TC_OBJ_ATTR 1
>  #endif
>  
> +#endif /* OBJ_ELF */
> +
>  #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 be44119dd92..28e7e4efb92 100644
> --- a/gas/config/obj-elf.c
> +++ b/gas/config/obj-elf.c
> @@ -73,7 +73,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);
> @@ -120,7 +122,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.  */
> @@ -2050,13 +2054,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 */
>  
> @@ -2997,7 +3008,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 */
>  }
>  
> @@ -3016,9 +3029,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 13649e8f3d5..5019ba20c08 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -2401,6 +2401,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
> @@ -2481,6 +2495,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/doc/c-aarch64.texi b/gas/doc/c-aarch64.texi
> index d7e9c95111d..d88fcdf2da7 100644
> --- a/gas/doc/c-aarch64.texi
> +++ b/gas/doc/c-aarch64.texi
> @@ -460,6 +460,29 @@ 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_-]+}.
> +
> +@var{comprehension} determines whether the subsection is @code{required} or
> +@code{optional}. An optional subsection can be skipped if it is not known by the
> +customer tool, unknown @code{required} subsection should generate an error and
> +stop the processing.

I think it would be clearer to write something like:

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 exhaustive 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 e218e07fa73..c076174a7ac 100644
> --- a/include/elf/aarch64.h
> +++ b/include/elf/aarch64.h
> @@ -57,6 +57,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)

R.



More information about the Binutils mailing list