[v0,03/15] gas: parse object attributes v2

Message ID 20250310175131.1217374-4-matthieu.longo@arm.com
State New
Headers
Series AArch64 AEABI build attributes (a.k.a. object attributes v2) |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 fail Patch failed to apply

Commit Message

Matthieu Longo March 10, 2025, 5:51 p.m. UTC
  From: Richard Ball <richard.ball@arm.com>

Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
---
 gas/config/obj-elf.c    | 411 ++++++++++++++++++++++++++++++++++++++++
 gas/config/obj-elf.h    |  27 +++
 gas/config/tc-aarch64.c | 102 ++++++++++
 gas/config/tc-aarch64.h |   5 +
 4 files changed, 545 insertions(+)
  

Comments

Jan Beulich March 11, 2025, 8:03 a.m. UTC | #1
On 10.03.2025 18:51, Matthieu Longo wrote:
> From: Richard Ball <richard.ball@arm.com>
> 
> Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
> ---
>  gas/config/obj-elf.c    | 411 ++++++++++++++++++++++++++++++++++++++++
>  gas/config/obj-elf.h    |  27 +++
>  gas/config/tc-aarch64.c | 102 ++++++++++
>  gas/config/tc-aarch64.h |   5 +
>  4 files changed, 545 insertions(+)

Same as patch 2 - this needs to come with a description. That description would
include explanation of the split between generic ELF and Arm64 specific code.
It's not obvious to me whether any other architecture really would want to
re-use the machinery. Plus ...

> --- a/gas/config/obj-elf.c
> +++ b/gas/config/obj-elf.c
> @@ -41,6 +41,10 @@
>  #include "elf/alpha.h"
>  #endif
>  
> +#ifdef TC_AARCH64
> +#include "elf/aarch64.h"
> +#endif
> +
>  #ifdef TC_MIPS
>  #include "elf/mips.h"
>  #endif

... despite pre-existing examples like the MIPS one here I don't think we
should lightly accept further arch-specific code in supposedly arch-agnostic
files.

> @@ -2127,6 +2131,413 @@ obj_elf_seen_attribute (int vendor, unsigned int tag)
>    return false;
>  }
>  
> +static bool
> +extract_string_literal (attribute_arg_value* arg_out)

Style nit (here and elsewhere): * and blank want to change places.

Jan
  
Matthieu Longo March 20, 2025, 2:16 p.m. UTC | #2
On 2025-03-11 08:03, Jan Beulich wrote:
> On 10.03.2025 18:51, Matthieu Longo wrote:
>> From: Richard Ball <richard.ball@arm.com>
>>
>> Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
>> ---
>>   gas/config/obj-elf.c    | 411 ++++++++++++++++++++++++++++++++++++++++
>>   gas/config/obj-elf.h    |  27 +++
>>   gas/config/tc-aarch64.c | 102 ++++++++++
>>   gas/config/tc-aarch64.h |   5 +
>>   4 files changed, 545 insertions(+)
> 
> Same as patch 2 - this needs to come with a description. That description would
> include explanation of the split between generic ELF and Arm64 specific code.
> It's not obvious to me whether any other architecture really would want to
> re-use the machinery. Plus ...
> 

Hopefully addressed in the next revision.

>> --- a/gas/config/obj-elf.c
>> +++ b/gas/config/obj-elf.c
>> @@ -41,6 +41,10 @@
>>   #include "elf/alpha.h"
>>   #endif
>>   
>> +#ifdef TC_AARCH64
>> +#include "elf/aarch64.h"
>> +#endif
>> +
>>   #ifdef TC_MIPS
>>   #include "elf/mips.h"
>>   #endif
> 
> ... despite pre-existing examples like the MIPS one here I don't think we
> should lightly accept further arch-specific code in supposedly arch-agnostic
> files.
> 

The reason why I had to include this header is because I need to call 
CONVERT_SYMBOLIC_ATTRIBUTE.
A refactoring would be needed in this area I guess. Instead of using 
this macro, there should be a method on a backend object similarly to 
what we have in bfd. I didn't find such a class for gas. Everything 
seems to be exposed via macros.

>> @@ -2127,6 +2131,413 @@ obj_elf_seen_attribute (int vendor, unsigned int tag)
>>     return false;
>>   }
>>   
>> +static bool
>> +extract_string_literal (attribute_arg_value* arg_out)
> 
> Style nit (here and elsewhere): * and blank want to change places.

Fixed.

> 
> Jan
  

Patch

diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c
index 0eb809cc15e..2d920942316 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -41,6 +41,10 @@ 
 #include "elf/alpha.h"
 #endif
 
+#ifdef TC_AARCH64
+#include "elf/aarch64.h"
+#endif
+
 #ifdef TC_MIPS
 #include "elf/mips.h"
 #endif
@@ -2127,6 +2131,413 @@  obj_elf_seen_attribute (int vendor, unsigned int tag)
   return false;
 }
 
+static bool
+extract_string_literal (attribute_arg_value* arg_out)
+{
+  skip_whitespace (input_line_pointer);
+
+  if (*input_line_pointer != '"')
+    {
+      as_bad ("Missing token '\"' for expected string: %c", *input_line_pointer);
+      return false;
+    }
+
+  arg_out->type = STRING_VALUE;
+  int len;
+  arg_out->value.string = demand_copy_C_string (&len);
+  if (len > 0)
+    {
+      arg_out->optional_set = true;
+      return true;
+    }
+
+  arg_out->value.string = NULL;
+  return false;
+}
+
+static bool
+extract_identifier (bool (*char_predicate) (char), attribute_arg_value* arg_out)
+{
+  expressionS exp;
+
+  /* Try to extract a value from the identifier.  */
+  if (ISDIGIT (*input_line_pointer))
+    {
+      expression (& exp);
+      if (exp.X_op != O_constant)
+	{
+	  as_bad (_("invalid value, expected an integer literal"));
+	  goto bad_integer_literal;
+	}
+      int val = exp.X_add_number;
+      arg_out->optional_set = true;
+      arg_out->type = ULEB128_VALUE;
+      arg_out->value.u64 = val;
+      return true;
+    }
+
+  /* Try to extract a symbol.  */
+  const char *s = input_line_pointer;
+  unsigned int i = 0;
+  for (; char_predicate (*input_line_pointer); ++input_line_pointer)
+    i++;
+  if (i == 0)
+    return false;
+  char* val = xmemdup0 (s, i);
+  arg_out->optional_set = true;
+  arg_out->type = STRING_VALUE;
+  arg_out->value.string = val;
+  return true;
+
+bad_integer_literal:
+  ignore_rest_of_line ();
+  return false;
+}
+
+typedef struct obj_attr_v2_known_identifier
+{
+  const char *name;
+  const uint32_t val;
+} obj_attr_v2_known_identifier;
+
+static bool
+obj_attr_v2_lookup_known_symbol (
+  const char *identifier,
+  const obj_attr_v2_known_identifier known_identifiers[],
+  const size_t N_identifiers,
+  uint32_t *val_out)
+{
+  if (identifier == NULL || known_identifiers == NULL || N_identifiers == 0)
+    return false;
+
+  for (size_t i = 0; i < N_identifiers; ++i)
+    if (!strcmp (known_identifiers[i].name, identifier))
+      {
+	*val_out = known_identifiers[i].val;
+	return true;
+      }
+  return false;
+}
+
+static int
+obj_attr_v2_look_symbol_table (const char *name)
+{
+  if (name == NULL)
+    return -1;
+
+  symbolS *symbolP;
+  symbolP = symbol_find (name);
+  if (symbolP == NULL)
+    return -1;
+
+  if (! S_IS_DEFINED (symbolP))
+    return -1;
+
+  valueT val = S_GET_VALUE (symbolP);
+  DEBUG_TRACE ("Resolved symbol '%s' to %lu", name, val);
+  return val;
+}
+
+static bool
+obj_attr_parse_arg (attribute_arg_type arg_type,
+		    const obj_attr_v2_known_identifier known_identifiers[],
+		    const size_t N_identifiers,
+		    bool skip_backend_symbol_attribute_lookup,
+		    attribute_arg_value *arg_out)
+{
+  bool match_symbol (char c)
+  {
+    return ISALNUM (c) || c == '_';
+  }
+
+  /* Note: for now, symbol look-up for string literals is not available.  */
+  if (arg_type == STRING_VALUE)
+    return extract_string_literal (arg_out);
+
+  /* Try to extract an identifier (symbol, integer literal).  */
+  if (! extract_identifier (&match_symbol, arg_out))
+    return false;
+
+  /* The identifier is fully resolved.  */
+  if (arg_out->type == ULEB128_VALUE)
+    return true;
+
+  /* The identifier is a symbol, let's try to resolve it using the provided list
+     of known symbols.  */
+  gas_assert (arg_out->type == STRING_VALUE);
+  uint32_t resolved_symbol_value;
+  if (obj_attr_v2_lookup_known_symbol (arg_out->value.string,
+				       known_identifiers,
+				       N_identifiers,
+				       &resolved_symbol_value))
+    {
+      free ((void *) arg_out->value.string);
+      arg_out->value.u64 = resolved_symbol_value;
+      arg_out->type = ULEB128_VALUE;
+      return true;
+    }
+
+  int resolved_symbol;
+  if (skip_backend_symbol_attribute_lookup)
+    {
+      /* Try to resolve the name using the symbol table for this compilation
+	 unit.  */
+      resolved_symbol = obj_attr_v2_look_symbol_table (arg_out->value.string);
+      if (resolved_symbol == -1)
+	goto bad;
+    }
+
+#ifndef CONVERT_SYMBOLIC_ATTRIBUTE
+  #define CONVERT_SYMBOLIC_ATTRIBUTE(a) -1
+#endif
+
+  /* Note: for now, CONVERT_SYMBOLIC_ATTRIBUTE can only handle integers.  */
+  resolved_symbol = CONVERT_SYMBOLIC_ATTRIBUTE (arg_out->value.string);
+  if (resolved_symbol == -1)
+    {
+      /* Try to resolve the name using the symbol table for this compilation
+	 unit.  */
+      resolved_symbol = obj_attr_v2_look_symbol_table (arg_out->value.string);
+      if (resolved_symbol == -1)
+	goto bad;
+    }
+
+  free ((void *) arg_out->value.string);
+  arg_out->value.u64 = resolved_symbol;
+  arg_out->type = ULEB128_VALUE;
+  return true;
+
+bad:
+  as_bad ("unknown symbol: %s", arg_out->value.string);
+  free ((void *) arg_out->value.string);
+  return false;
+}
+
+void
+obj_elf_obj_attr_v2_free_args (attribute_arg_value *args, size_t n)
+{
+  for (size_t i = 0; i < n; ++i)
+    if (args[i].type == STRING_VALUE && args[i].value.string != NULL)
+      free ((void *) args[i].value.string);
+  free (args);
+}
+
+attribute_arg_value *
+obj_elf_subsection_obj_attr_v2_parse_args (int nargs, ...)
+{
+  bool match_subsection_char (char c)
+  {
+    return ISALNUM (c) || c == '_' || c == '-';
+  }
+
+  va_list args;
+  va_start (args, nargs);
+
+  attribute_arg_value* attrs = calloc (nargs, sizeof (attribute_arg_value));
+
+  for (int count = 0; count < nargs; ++count)
+    {
+      skip_whitespace (input_line_pointer);
+
+      attribute_arg_type type_next_arg = va_arg (args, attribute_arg_type);
+
+      if (type_next_arg == SECTION_IDENTIFIER)
+	{
+	  if (!extract_identifier (&match_subsection_char, &attrs[count]))
+	    {
+	      as_bad (_("Invalid token for section name: %c"), *input_line_pointer);
+	      goto bad;
+	    }
+	  gas_assert (attrs[count].type == STRING_VALUE);
+	  attrs[count].type = SECTION_IDENTIFIER;
+	}
+      else if (type_next_arg == ULEB128_VALUE || type_next_arg == STRING_VALUE)
+	{
+	  const obj_attr_v2_known_identifier known_identifiers[] =
+	  {
+	    {"ULEB128", 0},
+	    {"uleb128", 0},
+	    {"NTBS", 1},
+	    {"ntbs", 1},
+	    {"optional", 1},
+	    {"required", 0},
+	  };
+	  if (! obj_attr_parse_arg (
+		  type_next_arg, known_identifiers,
+		  ARRAY_SIZE (known_identifiers),
+		  /* skip_backend_symbol_attribute_lookup */ true,
+		  &attrs[count]))
+	    goto bad;
+	}
+
+      if (count + 1 < nargs && skip_past_comma (&input_line_pointer) == -1)
+	goto bad;
+    }
+
+  va_end (args);
+  demand_empty_rest_of_line ();
+
+  return attrs;
+
+bad:
+  obj_elf_obj_attr_v2_free_args (attrs, nargs);
+  va_end (args);
+  as_bad (_("Expected <subsection_name>, <optional>, <format>"));
+  ignore_rest_of_line ();
+  return NULL;
+}
+
+attribute_arg_value *
+obj_elf_obj_attr_v2_parse_args (int nargs, ...)
+{
+  if (elf_obj_attr_subsections (stdoutput).last_ == NULL)
+    {
+      as_bad (_("A subsection has to be declared before declaring attributes"));
+      ignore_rest_of_line ();
+      return NULL;
+    }
+
+  va_list args;
+  va_start (args, nargs);
+
+  attribute_arg_value* attrs = calloc (nargs, sizeof (attribute_arg_value));
+
+  for (int count = 0; count < nargs; ++count)
+  {
+    skip_whitespace (input_line_pointer);
+
+    attribute_arg_type type_next_arg = va_arg (args, attribute_arg_type);
+
+    gas_assert (type_next_arg == ATTRIBUTE_KEY
+	     || type_next_arg == ATTRIBUTE_VALUE);
+
+    if (type_next_arg == ATTRIBUTE_KEY)
+      {
+	if (! obj_attr_parse_arg (
+		type_next_arg, NULL, 0,
+		/* skip_backend_symbol_attribute_lookup */ false,
+		&attrs[count]))
+	  goto bad;
+      }
+    else if (type_next_arg == ATTRIBUTE_VALUE)
+      {
+	obj_attr_encoding_v2 expected_value_type =
+	  elf_obj_attr_subsections (stdoutput).last_->encoding;
+	gas_assert (expected_value_type == ULEB128
+		 || expected_value_type == NTBS);
+
+	if (! obj_attr_parse_arg (
+		expected_value_type == ULEB128 ? ULEB128_VALUE : STRING_VALUE,
+		NULL, 0, /* skip_backend_symbol_attribute_lookup */ true,
+		&attrs[count]))
+	  goto bad;
+      }
+
+    if ((count + 1 < nargs) && (skip_past_comma (&input_line_pointer) == -1))
+      goto bad;
+  }
+
+  va_end (args);
+  demand_empty_rest_of_line ();
+
+  return attrs;
+
+bad:
+  obj_elf_obj_attr_v2_free_args (attrs, nargs);
+
+  va_end (args);
+  as_bad (_("expected <tag> , <value>"));
+  ignore_rest_of_line ();
+  return NULL;
+}
+
+void
+obj_elf_record_subsection_obj_attr_v2 (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;
+	}
+      /* 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)
+	? SUBSEC_PUBLIC
+	: 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);
+    }
+}
+
+void
+obj_elf_record_obj_attr_v2 (uint64_t key, attribute_arg_value *value)
+{
+  union obj_attr_value_v2 obj_attr_vals;
+  if (value->type == ULEB128_VALUE)
+    obj_attr_vals.uint_val = value->value.u64;
+  else
+    {
+      /* Move the string.  */
+      obj_attr_vals.string_val = xstrdup (value->value.string);
+      value->value.string = NULL;
+    }
+
+  obj_attr_v2* obj_attr = _bfd_elf_obj_attr_v2_init (key, obj_attr_vals);
+  if (value->type == STRING_VALUE)
+    obj_attr_vals.string_val = 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 ((value->type == ULEB128_VALUE
+	   && recorded_attr->vals.uint_val != obj_attr->vals.uint_val) ||
+	  (value->type == STRING_VALUE
+	   && 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 (value->type == STRING_VALUE && obj_attr->vals.string_val != NULL)
+	free ((void *) obj_attr->vals.string_val);
+      free (obj_attr);
+      return;
+    }
+
+  gas_assert (elf_obj_attr_subsections (stdoutput).last_ != NULL);
+  gas_assert (obj_attr != NULL);
+  LINKED_LIST_APPEND(obj_attr_v2) (
+    elf_obj_attr_subsections (stdoutput).last_, obj_attr);
+}
+
 /* Parse an attribute directive for VENDOR.
    Returns the attribute number read, or zero on error.  */
 
diff --git a/gas/config/obj-elf.h b/gas/config/obj-elf.h
index c8b57406685..5ef1701b5ac 100644
--- a/gas/config/obj-elf.h
+++ b/gas/config/obj-elf.h
@@ -54,6 +54,23 @@  extern int mips_flag_mdebug;
 #endif
 #endif
 
+typedef enum {
+  SECTION_IDENTIFIER,
+  ULEB128_VALUE,
+  STRING_VALUE,
+  ATTRIBUTE_KEY,
+  ATTRIBUTE_VALUE,
+} attribute_arg_type;
+
+typedef struct {
+    union {
+      const char* string;
+      uint64_t u64;
+    } value;
+    bool optional_set;
+    attribute_arg_type type;
+} attribute_arg_value;
+
 enum elf_visibility
 {
   visibility_unchanged = 0,
@@ -206,6 +223,16 @@  extern struct fix * obj_elf_get_vtable_inherit (void);
 extern struct fix * obj_elf_get_vtable_entry (void);
 extern bool obj_elf_seen_attribute
   (int, unsigned int);
+
+extern attribute_arg_value *
+  obj_elf_subsection_obj_attr_v2_parse_args (int nargs, ...);
+extern attribute_arg_value *
+  obj_elf_obj_attr_v2_parse_args (int nargs, ...);
+extern void obj_elf_obj_attr_v2_free_args (attribute_arg_value *, size_t);
+extern void obj_elf_record_subsection_obj_attr_v2 (const char *, bool,
+  obj_attr_encoding_v2);
+extern void obj_elf_record_obj_attr_v2 (uint64_t, attribute_arg_value *);
+
 extern int obj_elf_vendor_attribute (int);
 
 /* BFD wants to write the udata field, which is a no-no for the
diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
index ff40726ba75..4cad09c8e91 100644
--- a/gas/config/tc-aarch64.c
+++ b/gas/config/tc-aarch64.c
@@ -2401,6 +2401,56 @@  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)
+{
+  const size_t N_ARGS = 3;
+  attribute_arg_value* args =
+    obj_elf_subsection_obj_attr_v2_parse_args (
+      N_ARGS, SECTION_IDENTIFIER, ULEB128_VALUE, ULEB128_VALUE);
+
+  bool is_valid_boolean (uint64_t value)
+  {
+    return value == 0 || value == 1;
+  }
+
+  bool is_valid_optional (uint64_t value)
+  {
+    return value == 0 || value == 1;
+  }
+
+  if (args == NULL)
+    return;
+
+  if (! is_valid_boolean (args[1].value.u64))
+    as_bad (("Expected subsection values for <optional> are 0 or 1"));
+  if (! is_valid_optional (args[2].value.u64))
+    as_bad (("Expected subsection values for <format> are 0 or 1"));
+
+  obj_elf_record_subsection_obj_attr_v2 (args[0].value.string,
+					 args[1].value.u64,
+					 args[2].value.u64);
+
+  obj_elf_obj_attr_v2_free_args (args, N_ARGS);
+}
+
+/* Parse a .aeabi_attribute directive.  */
+static void
+s_aarch64_aeabi_attribute (int ignored ATTRIBUTE_UNUSED)
+{
+  const size_t N_ARGS = 2;
+  attribute_arg_value* args =
+    obj_elf_obj_attr_v2_parse_args (2, ATTRIBUTE_KEY, ATTRIBUTE_VALUE);
+
+  if (args == NULL)
+    return;
+
+  obj_elf_record_obj_attr_v2 (args[0].value.u64, &args[1]);
+
+  obj_elf_obj_attr_v2_free_args (args, N_ARGS);
+}
 #endif	/* OBJ_ELF */
 
 #ifdef TE_PE
@@ -2481,6 +2531,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},
@@ -11320,4 +11372,54 @@  aarch64_elf_copy_symbol_attributes (symbolS *dest, symbolS *src)
       S_SET_SIZE (dest, S_GET_SIZE (src));
     }
 }
+
+static bool
+aarch64_obj_attr_v2_lookup_known_symbol (const char *identifier, uint32_t *val)
+{
+  if (identifier == NULL)
+    return false;
+
+  /* IMPORTANT: This list must be alphabetically sorted.  */
+  static const struct
+  {
+    const char *name;
+    const uint32_t val;
+  } known_identifiers[] =
+  {
+    {"Tag_Feature_BTI", 0},
+    {"Tag_Feature_GCS", 2},
+    {"Tag_Feature_PAC", 1},
+    {"Tag_PAuth_Platform", 1},
+    {"Tag_PAuth_Schema", 2},
+  };
+
+  for (unsigned i = 0; i < ARRAY_SIZE (known_identifiers); ++i)
+    {
+      int cmp = strcmp (known_identifiers[i].name, identifier);
+      if (cmp == 0)
+	{
+	  *val = known_identifiers[i].val;
+	  return true;
+	}
+      else if (cmp > 0)
+	break;
+    }
+  return false;
+}
+
+/* Convert a symbol related to object attribute v2 to its corresponding
+   integer value.  */
+int
+aarch64_convert_symbolic_attribute (const char *name)
+{
+  if (name == NULL)
+    return -1;
+
+  uint32_t value = 0;
+  /* Try to resolve the name against a known list of identifiers.  */
+  if (aarch64_obj_attr_v2_lookup_known_symbol (name, &value))
+    return value;
+
+  return -1;
+}
 #endif
diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
index acf1ce4aa03..1499ec6938d 100644
--- a/gas/config/tc-aarch64.h
+++ b/gas/config/tc-aarch64.h
@@ -344,4 +344,9 @@  void tc_pe_dwarf2_emit_offset (symbolS *, unsigned int);
 
 #endif /* TE_PE */
 
+#ifdef OBJ_ELF
+#define CONVERT_SYMBOLIC_ATTRIBUTE(name) aarch64_convert_symbolic_attribute (name)
+extern int aarch64_convert_symbolic_attribute (const char *);
+#endif
+
 #endif /* TC_AARCH64 */