[PATCH v1 03/14] gas: implement parsing of object attributes v2

Matthieu Longo matthieu.longo@arm.com
Fri Mar 21 17:14:37 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, optionality, encoding
  Create or switch the current subsection to 'name'.
  Optionality 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 optionality 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.

** 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.
Given this difficulty, merging the parsing logic of OAv2 with the existing one
of OAv1 would complexify the code, its maintainance, and radical refactorings to
the existing code would create an important risk of introducing new bugs and
regressions. Consequently, I opted for keeping the two implementations separated
as much as possible.

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(+)

diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c
index d37d0fdc610..a72c51f7e81 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 840cf8ce4a2..1a61256cfd3 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 */
-- 
2.49.0



More information about the Binutils mailing list