[PATCH v1 11/14] Merge of Object Attributes v2 during linkage (generic logic)

Matthieu Longo matthieu.longo@arm.com
Fri Mar 21 17:14:45 GMT 2025


This patch adds all the generic logic to the linker to process OAv2.
The linker is an "advanced" consumer of OAv2. After parsing, it
deduplicates them, merge them, detect any compatibility issues,
and finally translate them to GNU properties.

** Overall design

The OAv2 processing pipeline follows a map-reduce pattern. Obviously,
the actual processing in GNU ld is not multi-threaded, and the
operations are not necessarily executed directly one after another.

* Phase 1, map: successive per-file operations applied on the list of
  compatible input objects.
  1. Parsing of the OAv2 section's data (also used by objcopy).
  2. Translation of relevant GNU properties to OAv2. This is required
     for the backward-compatibility with input objects only marked
     using GNU properties.
  3. Sorting of the subsections and object attributes. Further
     operations rely on the ordering to perform some optimization in
     the processing of the data.
  4. Deduplication of subsections and object attributes, and detection
     of any conflict between duplicated subsections or tags.
  5. Translation of relevant OAv2 to GNU properties for a
     forward-compatibility with the GNU properties merge.

* Phase 2, reduce: OAv2 in input objects are merged together.
  1. Gathering of "frozen" values (=coming from the command-line
     arguments) into a virtual read-only list of subsections and
     attributes.
  2. Merging of OAv2 from an input file and the frozen input.
  3. Merging of the results of step 2 together. Since the OAv2 merge
     is commutative and associative, it can be implemented as a reduce.
     However, GNU ld implements it as an accumulate because it does
     not support multithreading.
  Notes: the two merge phases also perform a marking of unsupported/
  invalid subsections and attributes. This marking can be used for
  debugging, and also more practically to drop unsupported optional
  subsections from the output.

* Phase 3, finalization of the output.
  1. Pruning of the unsupported/invalid subsections and attributes.
  2. Serialization of OAv2 data (also used by objcopy).
  Notes:
   - There is no translation of the merged OAv2 to GNU properties
     at this stage, as the GNU properties merge has already all the
     information that were translated in step 5 of stage 1.
   - The GNU properties are currently required as the runtime linker
     does not understand OAv2 yet.
   - Phase 3 should also include a compatibility check between the
     final merge result of the current link unit and input shared
     objects. I opted for postponing this compatibility check, and
     GNU properties merge will take care of it as it already does.

** Required subsections

Required subsections are processed slightly differently from the
optional subsections, as they cannot be pruned since they are mandatory,
hence an error will be raised by the linker if it is not recognized.

For now, the subsection for PAuth ABI is the only one use case, and
no merge is applied on the values. The values simply need to match.
This implementation choice might be challenged in the future if required
subsections can have the same diversity as optional subsections. If the
case arises, the refactoring to handle this new behavior should consists
in adding a new merge policy MERGE-EQUAL, or something similar. Some "if
required" should be added in the optional subsections merge logic to
error on any missing elements, or mismatch, and messages should also be
rephrased to point out that the error is for a required subsection.

** Important note regarding support for testing

In order to test this generic logic, AArch64's use cases are not
offering enough coverage, so a "GNU testing namespace" which corresponds
to the name of the subsection was introduced. It follows the following
pattern:
  gnu-testing-<XXXXXX>-MERGE-<POLICY>
with:
  - <XXXXXX>: an arbitrary name for your testing subsection.
  - <POLICY>: the name of the merging policy to apply on the values in
    the subsection. The currently supported merge policy are:
      * -MERGE-AND: bitwise AND applied on numerical values.
      * -MERGE-OR: bitwise OR applied on numerical values.
      * -MERGE-ADD: concatenates strings together with a '+' in-between.
    Note: "-MERGE-ADD" does not make really sense, and will very likely
    never be used for a real merge. Its only purpose is to test the
    correct handling of merges with strings.
Any subsection name matching neither names supported by the backend, nor
following the pattern corresponding GNU testing namespace will be considered
unknown and its status set to obj_attr_subsection_v2_unknown. This will
have for consequence the pruning of this subsection.

Additionally, the first two tags in gnu-testing namespace, GNUTestTag_0
and GNUTestTag_1, 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_v2_unknown. This behavior of the testing tags allows to test
the pruning of unknown attributes.
---
 bfd/elf-attrs.c    | 1424 ++++++++++++++++++++++++++++++++++++++++++++
 bfd/elf-attrs.h    |   76 +++
 bfd/elf-bfd.h      |   33 +
 bfd/elf.c          |   15 +-
 bfd/elfxx-target.h |   32 +
 ld/ldelf.c         |    1 +
 6 files changed, 1576 insertions(+), 5 deletions(-)

diff --git a/bfd/elf-attrs.c b/bfd/elf-attrs.c
index b1be9472645..3d63791c87b 100644
--- a/bfd/elf-attrs.c
+++ b/bfd/elf-attrs.c
@@ -382,6 +382,1399 @@ bfd_elf_set_obj_attr_contents (bfd *abfd, bfd_byte *buffer, bfd_vma size)
     write_obj_attr_section_v1 (abfd, buffer, size);
 }
 
+typedef struct
+{
+  bfd *pbfd;
+  bool has_build_attributes;
+  asection* sec;
+} bfd_search_result_t;
+
+static bfd_search_result_t
+bfd_linear_search_one_with_build_attributes (struct bfd_link_info *info)
+{
+  bool
+  bfd_has_build_attributes (bfd *abfd, bfd_search_result_t *res)
+  {
+    if (elf_obj_attr_subsections (abfd).size == 0)
+      return false;
+    res->has_build_attributes = true;
+
+    const char *sec_name = get_elf_backend_data (abfd)->obj_attrs_section;
+    if ((res->sec = bfd_get_section_by_name (abfd, sec_name)) == NULL)
+      return false;
+    return true;
+  }
+
+  const struct elf_backend_data *output_bfd
+    = get_elf_backend_data (info->output_bfd);
+  unsigned int elfclass = output_bfd->s->elfclass;
+  int elf_machine_code = output_bfd->elf_machine_code;
+
+  bool
+  bfd_is_non_dynamic_elf_object (bfd *abfd)
+  {
+    return (bfd_get_flavour (abfd) == bfd_target_elf_flavour)
+      && (abfd->section_count != 0)
+      && ((abfd->flags & DYNAMIC) == 0)
+      && (elf_machine_code == get_elf_backend_data (abfd)->elf_machine_code)
+      && (elfclass == get_elf_backend_data (abfd)->s->elfclass);
+  }
+
+  bfd_search_result_t res = {
+    .pbfd = NULL,
+    .has_build_attributes = false,
+    .sec = NULL,
+  };
+
+  for (bfd *abfd = info->input_bfds; abfd != NULL; abfd = abfd->link.next)
+    if (bfd_is_non_dynamic_elf_object (abfd))
+      {
+	res.pbfd = abfd;
+	if (bfd_has_build_attributes (abfd, &res))
+	  break;
+      }
+  return res;
+}
+
+/* Create a build attributes section for the given bfd input.  */
+static asection *
+create_build_attributes_section (struct bfd_link_info *info,
+				 bfd *ebfd)
+{
+  asection *sec;
+  const char* sec_name = get_elf_backend_data (ebfd)->obj_attrs_section;
+  sec = bfd_make_section_with_flags (ebfd,
+				     sec_name,
+				     (SEC_READONLY
+				      | SEC_HAS_CONTENTS
+				      | SEC_DATA));
+  if (sec == NULL)
+    info->callbacks->einfo (
+      _("%F%P: failed to create %s section\n"), sec_name);
+
+  unsigned align = (bfd_get_mach (ebfd) & bfd_mach_aarch64_ilp32) ? 2 : 3;
+  if (!bfd_set_section_alignment (sec, align))
+    info->callbacks->einfo (_("%F%pA: failed to align section\n"), sec);
+
+  elf_section_type (sec) = get_elf_backend_data (ebfd)->obj_attrs_section_type;
+
+  bfd_set_section_size (sec, bfd_elf_obj_attr_size (ebfd));
+
+  return sec;
+}
+
+/* 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 known_tag_v2 known_tags_gnu_testing [] =
+{
+  {0, "GNUTestTag_0", .default_value = {0}},
+  {1, "GNUTestTag_1", .default_value = {0}},
+};
+
+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 = ULEB128,
+    .len = sizeof (known_tags_gnu_testing) / sizeof (known_tag_v2),
+  },
+  /* Note for the future: GNU subsections can be added here below.  */
+};
+
+static bool
+gnu_testing_namespace (const char *subsec_name)
+{
+  return strncmp ("gnu-testing-", subsec_name, 12) == 0;
+}
+
+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;
+}
+
+static const known_tag_v2 *
+identify_tag (const known_subsection_v2* subsec, uint32_t tag)
+{
+  for (unsigned i = 0; i < subsec->len; ++i)
+    {
+      const known_tag_v2 *known_tag = &subsec->known_tags[i];
+      if (known_tag->tag == tag)
+	return known_tag;
+      else if (known_tag->tag > tag)
+	break;
+    }
+  return NULL;
+}
+
+const known_tag_v2 *
+known_obj_attr_v2_find_by_tag (const struct elf_backend_data *be,
+			       const char* subsec_name, uint32_t tag)
+{
+  const known_subsection_v2 *subsec_info =
+    identify_subsection (be, subsec_name);
+  if (subsec_info != NULL)
+    {
+      const known_tag_v2 *tag_info = identify_tag (subsec_info, tag);
+      return tag_info;
+    }
+  return NULL;
+}
+
+const char *
+obj_attr_v2_tag_to_string (const struct elf_backend_data *be,
+			   const char* subsec_name,
+			   uint32_t tag)
+{
+  const known_tag_v2 *tag_info =
+    known_obj_attr_v2_find_by_tag (be, subsec_name, tag);
+  if (tag_info != NULL)
+    return tag_info->name;
+  return NULL;
+}
+
+static void
+obj_attr_v2_overwrite_with_default (struct bfd_link_info *info,
+				    obj_attr_subsection_v2 *subsec,
+				    obj_attr_v2 *attr)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (info->output_bfd);
+
+  const known_tag_v2 *tag_info =
+    known_obj_attr_v2_find_by_tag (be, subsec->name, attr->tag);
+  if (tag_info == NULL)
+    {
+      attr->status = obj_attr_v2_unknown;
+      if (subsec->encoding == ULEB128)
+	attr->vals.uint_val = 0;
+      else
+	attr->vals.string_val = NULL;
+      return;
+    }
+
+  if (be->obj_attr_v2_default_value != NULL
+   && be->obj_attr_v2_default_value (info, tag_info, subsec, attr))
+    {}
+  else if (subsec->encoding == NTBS)
+    {
+      if (tag_info->default_value.string_val != NULL)
+	{
+	  if (attr->vals.string_val != NULL)
+	    free ((void *) attr->vals.string_val);
+	  attr->vals.string_val = strdup (tag_info->default_value.string_val);
+	}
+      else
+	attr->vals.string_val = NULL;
+    }
+  else
+    attr->vals.uint_val = tag_info->default_value.uint_val;
+}
+
+static obj_attr_v2 *
+obj_attr_v2_tag_default (struct bfd_link_info *info,
+			 obj_attr_subsection_v2 *subsec,
+			 obj_attr_v2 *attr)
+{
+  obj_attr_v2 *new_attr = _bfd_elf_obj_attr_v2_copy (attr, subsec->encoding);
+  obj_attr_v2_overwrite_with_default (info, subsec, new_attr);
+  return new_attr;
+}
+
+/* The currently supported merge policy in the testing GNU namespace.
+   - bitwise AND: apply bitwise AND.
+   - bitwise OR: apply bitwise OR.
+   - String-ADD: concatenates strings together with a '+' in-between.
+   Note: Such policies should only be used for testing.  */
+typedef enum {
+  SUBSECTION_TESTING_MERGE_UNSUPPORTED = 0,
+  SUBSECTION_TESTING_MERGE_AND_POLICY = 1,
+  SUBSECTION_TESTING_MERGE_OR_POLICY = 2,
+  SUBSECTION_TESTING_MERGE_ADD_POLICY = 3,
+} gnu_testing_merge_policy;
+
+/* The GNU policy are detected from the name of the subsection. It should follow
+   the following pattern: "gnu-testing-XXXXXX-MERGE-<POLICY>".  */
+static gnu_testing_merge_policy
+gnu_testing_merge_subsection (const char *subsec_name)
+{
+  if (! gnu_testing_namespace (subsec_name))
+    return SUBSECTION_TESTING_MERGE_UNSUPPORTED;
+
+  size_t subsec_name_len = strlen (subsec_name);
+  if (strcmp ("-MERGE-AND", subsec_name + subsec_name_len - 10) == 0)
+    return SUBSECTION_TESTING_MERGE_AND_POLICY;
+  else if (strcmp ("-MERGE-OR", subsec_name + subsec_name_len - 9) == 0)
+    return SUBSECTION_TESTING_MERGE_OR_POLICY;
+  else if (strcmp ("-MERGE-ADD", subsec_name + subsec_name_len - 10) == 0)
+    return SUBSECTION_TESTING_MERGE_ADD_POLICY;
+  else
+    return SUBSECTION_TESTING_MERGE_UNSUPPORTED;
+}
+
+/* Merge policy Integer-AND: apply bitwise AND between REF and RHS.  */
+obj_attr_v2_merge_result
+obj_attr_v2_tag_merge_AND (struct bfd_link_info *info,
+			   bfd *abfd,
+			   obj_attr_subsection_v2 *subsec,
+			   obj_attr_v2 *ref, obj_attr_v2 *rhs,
+			   obj_attr_v2 *frozen)
+{
+  (void) info;
+  (void) abfd;
+  (void) frozen;
+
+  BFD_ASSERT (subsec->encoding == ULEB128);
+
+  obj_attr_v2_merge_result res = {
+    .merge = true,
+    .vals.uint_val = 0,
+    .reason = NONE,
+  };
+
+  uint32_t original_value = ref->vals.uint_val;
+  res.vals.uint_val = (ref->vals.uint_val & rhs->vals.uint_val);
+  res.merge = (res.vals.uint_val != original_value);
+  if (res.vals.uint_val == original_value)
+    res.reason = SAME_VALUE_AS_REF;
+
+  return res;
+}
+
+/* Merge policy Integer-OR: apply bitwise OR between REF and RHS.  */
+static obj_attr_v2_merge_result
+obj_attr_v2_tag_merge_OR (struct bfd_link_info *info,
+			  bfd *abfd,
+			  obj_attr_subsection_v2 *subsec,
+			  obj_attr_v2 *ref, obj_attr_v2 *rhs,
+			  obj_attr_v2 *frozen)
+{
+  (void) info;
+  (void) abfd;
+  (void) frozen;
+
+  BFD_ASSERT (subsec->encoding == ULEB128);
+
+  obj_attr_v2_merge_result res = {
+    .merge = true,
+    .vals.uint_val = 0,
+    .reason = NONE,
+  };
+
+  uint32_t original_value = ref->vals.uint_val;
+  res.vals.uint_val = (ref->vals.uint_val | rhs->vals.uint_val);
+  res.merge = (res.vals.uint_val != original_value);
+  if (res.vals.uint_val == original_value)
+    res.reason = SAME_VALUE_AS_REF;
+
+  return res;
+}
+
+/* Merge policy String-ADD: concatenates strings together adding a '+' character
+   in-between.  */
+static obj_attr_v2_merge_result
+obj_attr_v2_tag_merge_ADD (struct bfd_link_info *info,
+			   bfd *abfd,
+			   obj_attr_subsection_v2 *subsec,
+			   obj_attr_v2 *ref, obj_attr_v2 *rhs,
+			   obj_attr_v2 *frozen)
+{
+  (void) info;
+  (void) abfd;
+  BFD_ASSERT (subsec->encoding == NTBS);
+
+  size_t frozen_s_size = 0;
+  if (frozen && frozen->vals.string_val)
+    frozen_s_size = strlen (frozen->vals.string_val);
+
+  obj_attr_v2_merge_result res = {
+    .merge = false,
+    .vals.uint_val = 0,
+    .reason = NONE,
+  };
+
+  if (ref->vals.string_val && rhs->vals.string_val)
+    {
+      res.merge = true;
+      size_t ref_s_size = strlen (ref->vals.string_val);
+      size_t rhs_s_size = strlen (rhs->vals.string_val);
+      char *buffer = malloc (ref_s_size + 1 + rhs_s_size + 1);
+      res.vals.string_val = buffer;
+      memcpy (buffer, ref->vals.string_val, ref_s_size);
+      buffer += ref_s_size;
+      *buffer = '+';
+      ++buffer;
+      memcpy (buffer, rhs->vals.string_val, rhs_s_size + 1);
+    }
+  else if (ref->vals.string_val)
+    {
+      // Nothing to do, frozen (if not NULL) should already be merged with it.
+      res.reason = SAME_VALUE_AS_REF;
+    }
+  else if (rhs->vals.string_val)
+    {
+      res.merge = true;
+      if (frozen_s_size == 0)
+	{
+	  res.vals.string_val = rhs->vals.string_val;
+	  rhs->vals.string_val = NULL;
+	}
+      else
+	{
+	  size_t rhs_s_size = strlen (rhs->vals.string_val);
+	  char *buffer = malloc (frozen_s_size + 1 + rhs_s_size + 1);
+	  res.vals.string_val = buffer;
+	  memcpy (buffer, frozen->vals.string_val, frozen_s_size);
+	  buffer += frozen_s_size;
+	  *buffer = '+';
+	  ++buffer;
+	  memcpy (buffer, rhs->vals.string_val, rhs_s_size + 1);
+	}
+    }
+  return res;
+}
+
+/* True, the tag needs to be merged, False don't merge.  */
+static
+obj_attr_v2_merge_result
+_bfd_elf_obj_attr_v2_tag_merge (struct bfd_link_info *info,
+				bfd *abfd,
+				obj_attr_subsection_v2 *subsec,
+				obj_attr_v2 *lhs, obj_attr_v2 *rhs,
+				obj_attr_v2 *frozen, bool frozen_as_abfd)
+{
+  obj_attr_v2_merge_result res = {
+    .merge = false,
+    .vals.uint_val = 0,
+    .reason = NONE,
+  };
+
+  gnu_testing_merge_policy policy;
+
+  if (get_elf_backend_data (abfd)->obj_attr_v2_tag_merge != NULL)
+    {
+      if (frozen_as_abfd)
+	{
+	  obj_attr_v2 *tmp = lhs;
+	  lhs = rhs;
+	  rhs = tmp;
+	}
+      res = get_elf_backend_data (abfd)->obj_attr_v2_tag_merge (info, abfd,
+	subsec, lhs, rhs, frozen);
+    }
+
+  /* Note for the future: the merge of generic object attributes should be
+     added here, between the architecture-specific merge, and the reserved GNU
+     testing namespace.  */
+
+  /* GNU testing merge policies are looked up last. If none is detected, the
+     subsection is considered unmergeable.  */
+  if (! res.merge && res.reason == UNSUPPORTED)
+    {
+      if ((policy = gnu_testing_merge_subsection (subsec->name))
+	   != SUBSECTION_TESTING_MERGE_UNSUPPORTED)
+	{
+	  /* Only the first two attributes can be merged, others won't and will
+	     be discarded.  */
+	  if (lhs->tag <= 1)
+	    {
+	      if (policy == SUBSECTION_TESTING_MERGE_AND_POLICY)
+		res = obj_attr_v2_tag_merge_AND (info, abfd, subsec, lhs, rhs,
+		  frozen);
+	      else if (policy == SUBSECTION_TESTING_MERGE_OR_POLICY)
+		res = obj_attr_v2_tag_merge_OR (info, abfd, subsec, lhs, rhs,
+		  frozen);
+	      else if (policy == SUBSECTION_TESTING_MERGE_ADD_POLICY)
+		res = obj_attr_v2_tag_merge_ADD (info, abfd, subsec, lhs, rhs,
+		  frozen);
+	    }
+	}
+    }
+
+  return res;
+}
+
+static void
+obj_attr_v2_default_append (struct bfd_link_info *info,
+			    obj_attr_subsection_v2 *s_abfd_missing,
+			    obj_attr_v2 *a_ref)
+{
+  obj_attr_v2 *new_attr = obj_attr_v2_tag_default (info, s_abfd_missing, a_ref);
+  LINKED_LIST_APPEND(obj_attr_v2) (s_abfd_missing, new_attr);
+}
+
+static obj_attr_subsection_v2 *
+obj_attr_subsection_v2_defaults (struct bfd_link_info *info,
+				 obj_attr_subsection_v2 *subsec)
+{
+  obj_attr_subsection_v2 *new_subsec =
+    _bfd_elf_obj_attr_subsection_v2_init (subsec->name, subsec->scope,
+      subsec->optional, subsec->encoding);
+
+  for (obj_attr_v2 *attr = subsec->first_;
+       attr != NULL;
+       attr = attr->next)
+    obj_attr_v2_default_append (info, new_subsec, attr);
+
+  return new_subsec;
+}
+
+static obj_attr_subsection_v2 *
+obj_attr_subsection_v2_perfect_match (struct bfd_link_info *info,
+				      bfd *ref_bfd, bfd *abfd,
+				      obj_attr_subsection_v2 *s_ref,
+				      obj_attr_subsection_v2 *s_abfd)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (abfd);
+
+  void
+  report_missing_required_obj_attr (bfd * ebfd, uint32_t tag)
+  {
+    const char* tag_s = obj_attr_v2_tag_to_string (be, s_ref->name, tag);
+    if (tag_s)
+      info->callbacks->einfo (
+	_("%X%pB: error: missing required object attribute '%s' in subsection "
+	  "'%s'\n" ), ebfd, tag_s, s_ref->name);
+    else
+      info->callbacks->einfo (
+	_("%X%pB: error: missing required object attribute 'Tag_unknown_%u' in "
+	  "subsection '%s'\n" ), ebfd, tag, s_ref->name);
+  }
+
+  void
+  report_mismatching_required_obj_attr (obj_attr_v2 *a_ref, obj_attr_v2 *a_abfd)
+  {
+    const char* tag_s = obj_attr_v2_tag_to_string (be, s_ref->name, a_ref->tag);
+    if (s_ref->encoding == ULEB128)
+    {
+      if (tag_s)
+	info->callbacks->einfo (
+	  _("%X%pB, %pB: error: mismatching values 0x%x and 0x%x for "
+	    "required object attribute '%s' in subsection '%s'\n"),
+	  ref_bfd, abfd, a_ref->vals.uint_val, a_abfd->vals.uint_val,
+	  obj_attr_v2_tag_to_string (be, s_ref->name, a_abfd->tag), s_ref->name);
+      else
+	info->callbacks->einfo (
+	  _("%X%pB, %pB: error: mismatching values 0x%x and 0x%x for "
+	    "required object attribute 'Tag_unknown_%u' in subsection '%s'\n"),
+	  ref_bfd, abfd, a_ref->vals.uint_val, a_abfd->vals.uint_val,
+	  a_abfd->tag, s_ref->name);
+    }
+    else
+    {
+      if (tag_s)
+	info->callbacks->einfo (
+	  _("%X%pB, %pB: error: mismatching values '%s' and '%s' for "
+	    "required object attribute '%s' in subsection '%s'\n"),
+	  ref_bfd, abfd, a_ref->vals.string_val, a_abfd->vals.string_val,
+	  obj_attr_v2_tag_to_string (be, s_ref->name, a_abfd->tag), s_ref->name);
+      else
+	info->callbacks->einfo (
+	  _("%X%pB, %pB: error: mismatching values '%s' and '%s' for "
+	    "required object attribute 'Tag_unknown_%u' in subsection '%s'\n"),
+	  ref_bfd, abfd, a_ref->vals.string_val, a_abfd->vals.string_val,
+	  a_abfd->tag, s_ref->name);
+    }
+  }
+
+  bool success = true;
+  obj_attr_v2 *a_ref = s_ref->first_;
+  obj_attr_v2 *a_abfd = s_abfd->first_;
+  while (a_ref != NULL && a_abfd != NULL)
+    {
+      if (a_ref->tag < a_abfd->tag)
+	{
+	  success = false;
+	  report_missing_required_obj_attr (abfd, a_ref->tag);
+	  a_ref = a_ref->next;
+	}
+      else if (a_ref->tag > a_abfd->tag)
+	{
+	  success = false;
+	  report_missing_required_obj_attr (ref_bfd, a_abfd->tag);
+	  a_abfd = a_abfd->next;
+	}
+      else
+	{
+	  if (s_ref->encoding == ULEB128)
+	    {
+	      if (a_ref->vals.uint_val != a_abfd->vals.uint_val)
+		{
+		  success = false;
+		  report_mismatching_required_obj_attr (a_ref, a_abfd);
+		}
+	    }
+	  else if (s_ref->encoding == NTBS)
+	    {
+	      if (strcmp (a_ref->vals.string_val, a_abfd->vals.string_val) != 0)
+		{
+		  success = false;
+		  report_mismatching_required_obj_attr (a_ref, a_abfd);
+		}
+	    }
+	  a_ref = a_ref->next;
+	  a_abfd = a_abfd->next;
+	}
+    }
+
+  for (; a_abfd != NULL; a_abfd = a_abfd->next)
+  {
+    success = false;
+    report_missing_required_obj_attr (ref_bfd, a_abfd->tag);
+  }
+
+  for (; a_ref != NULL; a_ref = a_ref->next)
+  {
+    success = false;
+    report_missing_required_obj_attr (abfd, a_ref->tag);
+  }
+
+  return success ? s_ref : NULL;
+}
+
+static obj_attr_v2 *
+search_obj_attr_v2_by_tag (obj_attr_v2 *attr_first, uint32_t tag)
+{
+  for (obj_attr_v2* attr = attr_first; attr != NULL; attr = attr->next)
+    {
+      if (attr->tag == tag)
+	return attr;
+      else if (attr->tag > tag)
+	break;
+    }
+  return NULL;
+}
+
+static
+obj_attr_subsection_v2 *
+handle_optional_subsection_merge (struct bfd_link_info *info,
+				  bfd *ref_bfd, bfd *abfd,
+				  obj_attr_subsection_v2 *s_ref,
+				  obj_attr_subsection_v2 *s_abfd,
+				  obj_attr_subsection_v2 *s_frozen)
+{
+  (void) info;
+  bool frozen_as_abfd = (ref_bfd == abfd);
+  obj_attr_v2 *a_ref = s_ref->first_;
+  obj_attr_v2 *a_abfd = s_abfd->first_;
+  obj_attr_v2 *a_frozen_first = (s_frozen != NULL) ? s_frozen->first_ : NULL;
+  while (a_ref != NULL && a_abfd != NULL)
+    {
+      uint32_t searched_frozen_tag =
+	(a_ref->tag < a_abfd->tag)
+	? a_ref->tag
+	: a_abfd->tag;
+      obj_attr_v2 *a_frozen =
+	search_obj_attr_v2_by_tag (a_frozen_first, searched_frozen_tag);
+      if (a_frozen != NULL)
+	a_frozen_first = a_frozen;
+
+      if (a_ref->tag < a_abfd->tag)
+	{
+	  obj_attr_v2 *a_default = obj_attr_v2_tag_default (info, s_abfd, a_ref);
+	  obj_attr_v2_merge_result res =
+	    _bfd_elf_obj_attr_v2_tag_merge (info, abfd, s_ref, a_ref, a_default,
+	      a_frozen, frozen_as_abfd);
+	  _bfd_elf_obj_attr_v2_free (a_default, s_ref->encoding);
+	  if (res.merge)
+	    a_ref->vals = res.vals;
+	  else if (res.reason == UNSUPPORTED)
+	    a_ref->status = obj_attr_v2_unknown;
+	  a_ref = a_ref->next;
+	}
+      else if (a_ref->tag > a_abfd->tag)
+	{
+	  obj_attr_v2 *a_default = obj_attr_v2_tag_default (info, s_ref, a_abfd);
+	  obj_attr_v2_merge_result res =
+	    _bfd_elf_obj_attr_v2_tag_merge (info, abfd, s_ref, a_default, a_abfd,
+	      a_frozen, frozen_as_abfd);
+	  if (res.merge || res.reason == SAME_VALUE_AS_REF)
+	    {
+	      a_default->vals = res.vals;
+	      LINKED_LIST_INSERT_BEFORE(obj_attr_v2) (s_ref, a_default, a_ref);
+	    }
+	  else
+	    _bfd_elf_obj_attr_v2_free (a_default, s_ref->encoding);
+	  a_abfd = a_abfd->next;
+	}
+      else
+	{
+	  obj_attr_v2_merge_result res =
+	    _bfd_elf_obj_attr_v2_tag_merge (info, abfd, s_ref, a_ref, a_abfd,
+	      a_frozen, frozen_as_abfd);
+	  if (res.merge)
+	    a_ref->vals = res.vals;
+	  else if (res.reason == UNSUPPORTED)
+	    a_ref->status = obj_attr_v2_unknown;
+	  a_ref = a_ref->next;
+	  a_abfd = a_abfd->next;
+	}
+    }
+
+  for (; a_abfd != NULL; a_abfd = a_abfd->next)
+    {
+      obj_attr_v2 *a_default = obj_attr_v2_tag_default (info, s_ref, a_abfd);
+      obj_attr_v2_merge_result res =
+	_bfd_elf_obj_attr_v2_tag_merge (info, abfd, s_ref, a_default, a_abfd,
+	  NULL, false);
+      if (res.merge || res.reason == SAME_VALUE_AS_REF)
+	{
+	  a_default->vals = res.vals;
+	  LINKED_LIST_APPEND(obj_attr_v2) (s_ref, a_default);
+	}
+      else
+	_bfd_elf_obj_attr_v2_free (a_default, s_ref->encoding);
+    }
+
+  for (; a_ref != NULL; a_ref = a_ref->next)
+    {
+      obj_attr_v2 *a_frozen = search_obj_attr_v2_by_tag (a_frozen_first, a_ref->tag);
+      if (a_frozen != NULL)
+	a_frozen_first = a_frozen;
+
+      obj_attr_v2 *a_default = obj_attr_v2_tag_default (info, s_abfd, a_ref);
+      obj_attr_v2_merge_result res =
+	_bfd_elf_obj_attr_v2_tag_merge (info, abfd, s_ref, a_ref, a_default,
+	  a_frozen, frozen_as_abfd);
+      _bfd_elf_obj_attr_v2_free (a_default, s_ref->encoding);
+      if (res.merge)
+	a_ref->vals = res.vals;
+      else if (res.reason == UNSUPPORTED)
+	a_ref->status = obj_attr_v2_unknown;
+    }
+
+  return s_ref;
+}
+
+static obj_attr_subsection_v2 *
+handle_subsection_merge (struct bfd_link_info *info, bfd *ref_bfd, bfd *abfd,
+  obj_attr_subsection_v2 *s_ref, obj_attr_subsection_v2 *s_abfd,
+  obj_attr_subsection_v2 *s_frozen)
+{
+  if (! s_ref->optional)
+    return obj_attr_subsection_v2_perfect_match (info, ref_bfd, abfd, s_ref, s_abfd);
+  return handle_optional_subsection_merge (info, ref_bfd, abfd, s_ref, s_abfd, s_frozen);
+}
+
+static bool
+handle_subsection_missing (struct bfd_link_info *info, bfd *ref_bfd, bfd *abfd,
+  obj_attr_subsection_v2 *s_ref, obj_attr_subsection_v2 *s_frozen)
+{
+  if (! s_ref->optional)
+    {
+      info->callbacks->einfo (_("%X%pB: error: missing required object "
+	"attributes subsection %s\n" ), abfd, s_ref->name);
+      return false;
+    }
+
+  /* Compute default values of the missing attributes in ABFD, but present in
+     REF, and merge ABFD's generated subsection with the one of REF.  */
+  obj_attr_subsection_v2 *s_abfd =
+    obj_attr_subsection_v2_defaults (info, s_ref);
+  obj_attr_subsection_v2 *merged =
+    handle_subsection_merge (info, ref_bfd, abfd, s_ref, s_abfd, s_frozen);
+  _bfd_elf_obj_attr_subsection_v2_free (s_abfd);
+  return merged != NULL;
+}
+
+static bool
+handle_subsection_additional (struct bfd_link_info *info, bfd *ref_bfd, bfd *abfd,
+  obj_attr_subsection_v2 *s_ref_next, obj_attr_subsection_v2 *s_abfd,
+  obj_attr_subsection_v2 *s_frozen)
+{
+  if (! s_abfd->optional)
+    {
+      info->callbacks->einfo (_("%X%pB: error: missing required object "
+	"attributes subsection %s\n" ), ref_bfd, s_abfd->name);
+      return false;
+    }
+
+  /* Compute default values of the missing attributes in REF, but present in
+     ABFD, and merge REF's generated subsection with the one of ABFD.  */
+  obj_attr_subsection_v2 *s_ref =
+    obj_attr_subsection_v2_defaults (info, s_abfd);
+  obj_attr_subsection_v2 *s_merged =
+    handle_subsection_merge (info, ref_bfd, abfd, s_ref, s_abfd, s_frozen);
+  BFD_ASSERT (s_merged == s_ref); // FIXME: I am not sure whether that it is true or false. If true, eliminate next free.
+  if (s_ref != s_merged)
+    _bfd_elf_obj_attr_subsection_v2_free (s_ref);
+  if (s_merged != NULL)
+    {
+      LINKED_LIST_INSERT_BEFORE(obj_attr_subsection_v2) (
+	&elf_obj_attr_subsections (ref_bfd), s_merged, s_ref_next);
+    }
+  return (s_merged != NULL);
+}
+
+const char *
+obj_attr_subsection_v2_optional_to_string (bool optional)
+{
+  return optional ? "optional" : "required";
+}
+
+const char *
+obj_attr_encoding_v2_to_string (obj_attr_encoding_v2 encoding)
+{
+  return (encoding == ULEB128) ? "uleb128" : "ntbs";
+}
+
+static bool
+check_mismatching_parameters (struct bfd_link_info *info, bfd *f1, bfd *f2,
+			      obj_attr_subsection_v2 *s1,
+			      obj_attr_subsection_v2 *s2)
+{
+  if (! gnu_testing_namespace (s1->name))
+    {
+      /* Check whether the subsection is known, and if so, match against the
+	 expected properties.
+	 Note: this piece of code must be guarded against gnu-testing
+	 subsections, as the backend method looks up at the known subsections.
+	 Since the "fictive" entry for gnu-testing known subsection has random
+	 values for its encoding and optionality, it won't be able to detect
+	 mismatching parameters correctly.  */
+      bool match_known = true;
+      if (get_elf_backend_data (f2)->obj_attr_subsection_v2_match_known != NULL)
+	match_known = get_elf_backend_data (f2)
+	  ->obj_attr_subsection_v2_match_known (info, f2, s2);
+      if (! match_known)
+	return true;
+    }
+
+  bool mismatch = (s1->encoding != s2->encoding)
+	       || (s1->optional != s2->optional);
+
+  if (mismatch)
+    {
+      if (f1 != NULL)
+	info->callbacks->einfo (_("%X%pB, %pB: error: parameters of subsection"
+	  " '%s' are mismatching. (%s, %s) VS (%s, %s)\n"), f1, f2, s1->name,
+	  obj_attr_subsection_v2_optional_to_string (s1->optional),
+	  obj_attr_encoding_v2_to_string (s1->encoding),
+	  obj_attr_subsection_v2_optional_to_string (s2->optional),
+	  obj_attr_encoding_v2_to_string (s2->encoding));
+      else
+	info->callbacks->einfo (_("%X%pB: error: parameters of subsection"
+	  " '%s' are corrupted. (%s, %s) VS (%s, %s)\n"), f2, s1->name,
+	  obj_attr_subsection_v2_optional_to_string (s1->optional),
+	  obj_attr_encoding_v2_to_string (s1->encoding),
+	  obj_attr_subsection_v2_optional_to_string (s2->optional),
+	  obj_attr_encoding_v2_to_string (s2->encoding));
+    }
+
+  return mismatch;
+}
+
+static bool
+obj_attr_subsection_v2_merge_frozen (struct bfd_link_info *info,
+				     bfd *abfd,
+				     obj_attr_subsection_v2 *s_frozen)
+{
+  if (s_frozen == NULL)
+    return true;
+
+  bool success = true;
+
+  obj_attr_subsection_v2 *s_abfd = elf_obj_attr_subsections (abfd).first_;
+  while (s_frozen != NULL && s_abfd != NULL)
+    {
+      int cmp = strcmp (s_abfd->name, s_frozen->name);
+      if (cmp < 0) /* ABFD has a subsection that FROZEN doesn't have.  */
+	{
+	  /* No need to try to merge anything here.  */
+	  s_abfd = s_abfd->next;
+	}
+      else if (cmp > 0) /* FROZEN has a subsection that ABFD doesn't have.  */
+	{
+	  success &= handle_subsection_additional (info, abfd, abfd,
+	    s_abfd, s_frozen, s_frozen);
+	  s_frozen = s_frozen->next;
+	}
+      else /* Both ABFD and frozen have the subsection.  */
+	{
+	  bool mismatch = check_mismatching_parameters (info, NULL, abfd,
+	    s_frozen, s_abfd);
+	  success &= ! mismatch;
+	  if (mismatch)
+	    /* FROZEN cannot be corrupted as it is generated from the command
+	       line arguments.  If it is corrupted, it is a bug.  */
+	    s_abfd->status = obj_attr_subsection_v2_corrupted;
+	  else
+	    success &= (handle_subsection_merge (info, abfd, abfd,
+	      s_abfd, s_frozen, s_frozen) != NULL);
+
+	  s_abfd = s_abfd->next;
+	  s_frozen = s_frozen->next;
+	}
+    }
+
+  /* No need to go through the remaining sections of ABFD, only mismatch against
+     FROZEN are interesting.  */
+
+  for (; s_frozen != NULL; s_frozen = s_frozen->next)
+    success &= handle_subsection_additional (info, abfd, abfd,
+      elf_obj_attr_subsections (abfd).last_, s_frozen, s_frozen);
+
+  return success;
+}
+
+static bool
+obj_attr_subsection_v2_merge (struct bfd_link_info *info,
+			      bfd *ref_bfd, bfd *abfd)
+{
+  bool success = true;
+  obj_attr_subsection_list *out_frozen_subsecs =
+    &elf_obj_attr_subsections (info->output_bfd);
+  obj_attr_subsection_list *abfd_subsecs = &elf_obj_attr_subsections (abfd);
+  obj_attr_subsection_list *ref_subsecs = &elf_obj_attr_subsections (ref_bfd);
+
+  obj_attr_subsection_v2 *s_frozen_first = out_frozen_subsecs->first_;
+  obj_attr_subsection_v2 *s_abfd = abfd_subsecs->first_;
+  obj_attr_subsection_v2 *s_ref = ref_subsecs->first_;
+
+  /* Translate object attributes from abfd to GNU properties if they have an
+     equivalence.  */
+  _bfd_elf_translate_relevant_obj_attrs_to_gnu_props (abfd);
+
+  while (s_abfd != NULL && s_ref != NULL)
+    {
+      int cmp = strcmp (s_ref->name, s_abfd->name);
+
+      if (cmp < 0) /* REF has a subsection that ABFD doesn't have.  */
+	{
+	  if (s_ref->status != obj_attr_subsection_v2_ok)
+	    {
+	      s_ref = s_ref->next;
+	      continue;
+	    }
+
+	  obj_attr_subsection_v2 *s_frozen =
+	    obj_attr_subsection_v2_find_by_name (s_frozen_first, s_ref->name, true);
+
+	  /* Mismatching between REF and FROZEN already done in
+	     obj_attr_subsection_v2_merge_frozen.  */
+	  success &= handle_subsection_missing (info, ref_bfd, abfd, s_ref,
+	    s_frozen);
+
+	  if (s_frozen != NULL)
+	    s_frozen_first = s_frozen->next;
+	  s_ref = s_ref->next;
+	}
+      else if (cmp > 0) /* ABFD has a subsection that REF doesn't have.  */
+	{
+	  if (s_abfd->status != obj_attr_subsection_v2_ok)
+	    {
+	      s_abfd = s_abfd->next;
+	      continue;
+	    }
+
+	  obj_attr_subsection_v2 *s_frozen =
+	    obj_attr_subsection_v2_find_by_name (s_frozen_first, s_abfd->name, true);
+	  if (s_frozen != NULL)
+	    {
+	      /* Check any mismatch against ABFD and FROZEN.  */
+	      bool mismatch = check_mismatching_parameters (info, NULL, abfd,
+		s_frozen, s_abfd);
+	      success &= ! mismatch;
+	      if (mismatch)
+		s_abfd->status = obj_attr_subsection_v2_corrupted;
+	      else
+		success &= handle_subsection_additional (info, ref_bfd, abfd,
+		  s_ref, s_abfd, s_frozen);
+	    }
+	  else
+	    success &= handle_subsection_additional (info, ref_bfd, abfd, s_ref,
+	      s_abfd, NULL);
+
+	  if (s_frozen != NULL)
+	    s_frozen_first = s_frozen->next;
+	  s_abfd = s_abfd->next;
+	}
+      else /* Both REF and ABFD have the subsection.  */
+	{
+	  if (s_ref->status != obj_attr_subsection_v2_ok)
+	    {
+	      s_ref = s_ref->next;
+	      s_abfd = s_abfd->next;
+	      continue;
+	    }
+
+	  obj_attr_subsection_v2 *s_frozen =
+	    obj_attr_subsection_v2_find_by_name (s_frozen_first, s_ref->name, true);
+	  if (s_frozen != NULL)
+	    {
+	      bool mismatch = check_mismatching_parameters (info, NULL, abfd,
+		s_frozen, s_abfd);
+	      success &= ! mismatch;
+	      if (mismatch)
+		s_abfd->status = obj_attr_subsection_v2_corrupted;
+	      else
+		{
+		  success &= (handle_subsection_merge (info, ref_bfd, abfd,
+						       s_ref, s_abfd, s_frozen)
+			      != NULL);
+		}
+	    }
+	  else
+	    {
+	      bool mismatch = check_mismatching_parameters (info, ref_bfd, abfd,
+		s_ref, s_abfd);
+	      success &= ! mismatch;
+	      if (mismatch)
+		s_abfd->status = obj_attr_subsection_v2_corrupted;
+	      else
+		success &= (handle_subsection_merge (info, ref_bfd, abfd, s_ref,
+						     s_abfd, NULL) != NULL);
+	    }
+
+	  if (s_frozen != NULL)
+	    s_frozen_first = s_frozen->next;
+	  s_ref = s_ref->next;
+	  s_abfd = s_abfd->next;
+	}
+    }
+
+  for (; s_abfd != NULL; s_abfd = s_abfd->next)
+    {
+      if (s_abfd->status != obj_attr_subsection_v2_ok)
+	continue;
+
+      obj_attr_subsection_v2 *s_frozen =
+	obj_attr_subsection_v2_find_by_name (s_frozen_first, s_abfd->name, true);
+      if (s_frozen != NULL)
+	{
+	  bool mismatch = check_mismatching_parameters (info, NULL, abfd,
+	    s_frozen, s_abfd);
+	  success &= ! mismatch;
+	  if (mismatch)
+	    s_abfd->status = obj_attr_subsection_v2_corrupted;
+	  else
+	    success &= handle_subsection_additional (info, ref_bfd, abfd,
+	      ref_subsecs->last_, s_abfd, s_frozen);
+	}
+      else
+	success &= handle_subsection_additional (info, ref_bfd, abfd,
+	  ref_subsecs->last_, s_abfd, NULL);
+    }
+
+  for (; s_ref != NULL; s_ref = s_ref->next)
+    {
+      if (s_ref->status != obj_attr_subsection_v2_ok)
+	continue;
+
+      obj_attr_subsection_v2 *s_frozen =
+	obj_attr_subsection_v2_find_by_name (s_frozen_first, s_ref->name, true);
+      /* No need to check for matching parameters here as frozen has already
+	 been checked against ref.  */
+      success &= handle_subsection_missing (info, ref_bfd, abfd, s_ref, s_frozen);
+    }
+
+  return success;
+}
+
+/* Compact duplicated tag declarations in a same subsection.  */
+static bool
+obj_attr_v2_compact_tags (bfd *abfd,
+			  obj_attr_subsection_v2 *subsec)
+{
+  bool success = true;
+
+
+  for (obj_attr_v2 *a = subsec->first_;
+       a != NULL && a->next != NULL;)
+    {
+      if (a->tag != a->next->tag)
+	{
+	  a = a->next;
+	  continue;
+	}
+
+      if (subsec->encoding == ULEB128)
+	{
+	  if (a->vals.uint_val != a->next->vals.uint_val)
+	    {
+	      success = false;
+	      _bfd_error_handler (_("%pB: error: found duplicated attributes "
+		"'Tag_unknown_%u' with conflicting values (0x%x vs 0x%x) in "
+		"subsection %s"), abfd, a->tag, a->vals.uint_val,
+		a->next->vals.uint_val, subsec->name);
+	    }
+	  else
+	    LINKED_LIST_REMOVE(obj_attr_v2) (subsec, a->next);
+	}
+      else /* (subsec->encoding == NTBS)  */
+	{
+	  if (strcmp (a->vals.string_val, a->next->vals.string_val) != 0)
+	    {
+	      success = false;
+	      _bfd_error_handler (_("%pB: error: found duplicated attributes "
+		"'Tag_unknown_%u' with conflicting values ('%s' vs '%s') in "
+		"subsection %s"), abfd, a->tag, a->vals.string_val,
+		a->next->vals.string_val, subsec->name);
+	    }
+	  else
+	    LINKED_LIST_REMOVE(obj_attr_v2) (subsec, a->next);
+	}
+    }
+
+  return success;
+}
+
+/* Merge two subsections together (object attributes v2 only).
+   The result is stored into subsec1.  subsec2 is destroyed.
+   Return true if the merge was successful, false otherwise.
+   Note: subsec1 and subsec2 are expected to be sorted before the call to this
+   function.  */
+static bool
+obj_attr_subsection_v2_destructive_merge (bfd *abfd,
+					  obj_attr_subsection_v2 *subsec1,
+					  obj_attr_subsection_v2 *subsec2)
+{
+  BFD_ASSERT (subsec1->encoding == subsec2->encoding
+	   && subsec1->optional == subsec2->optional);
+
+  bool success = true;
+
+  success &= obj_attr_v2_compact_tags (abfd, subsec1);
+  success &= obj_attr_v2_compact_tags (abfd, subsec2);
+
+  obj_attr_v2 *a1 = subsec1->first_;
+  obj_attr_v2 *a2 = subsec2->first_;
+  while (a1 != NULL && a2 != NULL)
+    {
+      if (a1->tag < a2->tag)
+	{} /* Nothing to do, a1 is already in subsec1.  */
+      else if (a1->tag > a2->tag)
+	{
+	  /* a2 is missing in subsec1, add it.  */
+	  obj_attr_v2 *previous = LINKED_LIST_REMOVE(obj_attr_v2) (subsec2, a2);
+	  LINKED_LIST_INSERT_BEFORE(obj_attr_v2) (subsec1, a2, a1);
+	  a2 = previous;
+	}
+      else
+	{
+	  if (subsec1->encoding == ULEB128
+	   && a1->vals.uint_val != a2->vals.uint_val)
+	    {
+	      success = false;
+	      _bfd_error_handler (_("%pB: error: found 2 subsections with the "
+		"same name '%s' and found conflicting values (0x%x vs 0x%x) for"
+		" object attribute 'Tag_unknown_%u'"), abfd, subsec1->name,
+		a1->vals.uint_val, a2->vals.uint_val, a1->tag);
+	    }
+	  else if (subsec1->encoding == NTBS
+	    && strcmp (a1->vals.string_val, a2->vals.string_val) != 0)
+	    {
+	      success = false;
+	      _bfd_error_handler (_("%pB: error: found 2 subsections with the "
+		"same name '%s' and found conflicting values ('%s' vs '%s') for"
+		" object attribute 'Tag_unknown_%u'"), abfd, subsec1->name,
+		a1->vals.string_val, a2->vals.string_val, a1->tag);
+	    }
+	}
+      a1 = a1->next;
+      a2 = a2->next;
+    }
+
+  for (; a2 != NULL; a2 = a2->next)
+    {
+      /* a2 is missing in subsec1, add it.  */
+      obj_attr_v2 *previous = LINKED_LIST_REMOVE(obj_attr_v2) (subsec2, a2);
+      LINKED_LIST_INSERT_BEFORE(obj_attr_v2) (subsec1, a2, a1);
+      a2 = previous;
+    }
+
+  /* If a1 != NULL, we don't care since it is already in subsec1.  */
+
+  /* Destroy subsec2 before exiting.  */
+  _bfd_elf_obj_attr_subsection_v2_free (subsec2);
+
+  return success;
+}
+
+/* Merge duplicated subsections and object attributes inside a same object
+   file.  After a call to this function, the subsections and object attributes
+   are sorted.  */
+static bool
+obj_attr_subsection_v2_file_scope_merge (bfd *abfd)
+{
+  obj_attr_subsection_list *subsecs = &elf_obj_attr_subsections (abfd);
+
+  /* Sort all the subsections and object attributes as they might not have been
+     inserted in the right order.  From now on, the subsections and attributes
+     will be assumed to be always sorted.  Any additive mutation will need to
+     preserve the order.  */
+  obj_attr_subsection_v2_sort (subsecs);
+
+  bool success = true;
+
+  obj_attr_subsection_v2 *subsec = subsecs->first_;
+  while (subsec != NULL && subsec->next != NULL)
+    {
+      obj_attr_subsection_v2 *dup_subsec =
+	obj_attr_subsection_v2_find_by_name (subsec->next, subsec->name, false);
+      if (dup_subsec != NULL)
+	{
+	  LINKED_LIST_REMOVE(obj_attr_subsection_v2) (subsecs, dup_subsec);
+	  success &= obj_attr_subsection_v2_destructive_merge (abfd, subsec,
+	    dup_subsec);
+	}
+      else
+	subsec = subsec->next;
+    }
+
+  return success;
+}
+
+static void
+mark_unknown_subsections (bfd *abfd)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (abfd);
+  for (obj_attr_subsection_v2* subsec = elf_obj_attr_subsections (abfd).first_;
+       subsec != NULL;
+       subsec = subsec->next)
+    {
+      if (identify_subsection (be, subsec->name) == NULL)
+	subsec->status = obj_attr_subsection_v2_unknown;
+    }
+}
+
+static bfd *
+bfd_elf_merge_build_attributes (struct bfd_link_info *info, bfd *ref_bfd)
+{
+  const struct elf_backend_data *be_out =
+    get_elf_backend_data (info->output_bfd);
+  unsigned int elfclass = be_out->s->elfclass;
+  int elf_machine_code = be_out->elf_machine_code;
+
+  bool
+  valid_elf_object (bfd *abfd)
+  {
+    return ((abfd->flags & (DYNAMIC | BFD_PLUGIN | BFD_LINKER_CREATED)) == 0)
+	&& (bfd_get_flavour (abfd) == bfd_target_elf_flavour)
+	&& (elf_machine_code == get_elf_backend_data (abfd)->elf_machine_code)
+	&& (elfclass == get_elf_backend_data (abfd)->s->elfclass);
+  }
+
+  mark_unknown_subsections (ref_bfd);
+
+  bool success = true;
+  for (bfd *abfd = info->input_bfds; abfd != NULL; abfd = abfd->link.next)
+    if (abfd != ref_bfd && valid_elf_object (abfd))
+      {
+	/* Convert relevant GNU properties to object attributes.  */
+	_bfd_elf_translate_relevant_gnu_props_to_obj_attrs (abfd);
+	/* Merge duplicates subsections and attributes.  */
+	success &= obj_attr_subsection_v2_file_scope_merge (abfd);
+
+	mark_unknown_subsections (abfd);
+	success &= obj_attr_subsection_v2_merge (info, ref_bfd, abfd);
+      }
+
+  return success ? ref_bfd : NULL;
+}
+
+/* Prune the given attribute, and return the next one.  */
+static obj_attr_v2 *
+obj_attr_v2_prune (obj_attr_subsection_v2 *subsec,
+		   obj_attr_v2 *attr)
+{
+  obj_attr_v2 *next = attr->next;
+  LINKED_LIST_REMOVE(obj_attr_v2) (subsec, attr);
+  _bfd_elf_obj_attr_v2_free (attr, subsec->encoding);
+  return next;
+}
+
+/* Prune the given subsection, and return the next one.  */
+static obj_attr_subsection_v2 *
+obj_attr_subsection_v2_prune (obj_attr_subsection_list *plist,
+			      obj_attr_subsection_v2 *subsec)
+{
+  obj_attr_subsection_v2 *next = subsec->next;
+    LINKED_LIST_REMOVE(obj_attr_subsection_v2) (plist, subsec);
+  _bfd_elf_obj_attr_subsection_v2_free (subsec);
+  return next;
+}
+
+static void
+info_obj_attr_v2_prune (struct bfd_link_info *info,
+			obj_attr_subsection_v2 *subsec,
+			obj_attr_v2 *attr)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (info->output_bfd);
+  const char *attr_s = obj_attr_v2_tag_to_string (be, subsec->name, attr->tag);
+  if (attr_s)
+    info->callbacks->minfo (_("Removed attribute '%s' from '%s'\n"), attr_s,
+      subsec->name);
+  else
+    info->callbacks->minfo (_("Removed attribute 'Tag_unknown_%u' from '%s'\n"),
+      attr->tag, subsec->name);
+}
+
+static void
+info_obj_attr_subsection_v2_prune (struct bfd_link_info *info,
+				   obj_attr_subsection_v2 *subsec)
+{
+  info->callbacks->minfo (_("Removed subsection '%s'\n"), subsec->name);
+}
+
+/* Prune any attributes with a status different from obj_attr_v2_ok in the
+   given subsection.  */
+static void
+obj_attr_v2_filter_prune (struct bfd_link_info *info,
+			  obj_attr_subsection_v2 *subsec)
+{
+  for (obj_attr_v2 *attr = subsec->first_;
+       attr != NULL;)
+    {
+      if (attr->status != obj_attr_v2_ok)
+	{
+	  info_obj_attr_v2_prune (info, subsec, attr);
+	  attr = obj_attr_v2_prune (subsec, attr);
+	}
+      else
+	attr = attr->next;
+    }
+}
+
+/* Prune any subsection with a status different from obj_attr_subsection_v2_ok
+   in the given list of subsections.  */
+static void
+obj_attr_subsection_v2_filter_prune (struct bfd_link_info *info,
+				     obj_attr_subsection_list *plist)
+{
+  for (obj_attr_subsection_v2 *subsec = plist->first_;
+       subsec != NULL;)
+    {
+      if (subsec->status == obj_attr_subsection_v2_ok)
+	obj_attr_v2_filter_prune (info, subsec);
+
+      if (subsec->size == 0 || subsec->status != obj_attr_subsection_v2_ok)
+	{
+	  info_obj_attr_subsection_v2_prune (info, subsec);
+	  subsec = obj_attr_subsection_v2_prune (plist, subsec);
+	}
+      else
+	subsec = subsec->next;
+    }
+}
+
+static bfd *
+bfd_elf_prune_build_attributes (struct bfd_link_info *info, bfd *abfd)
+{
+  if (abfd == NULL)
+    return NULL;
+
+  obj_attr_subsection_list *plist = &elf_obj_attr_subsections (abfd);
+  obj_attr_subsection_v2_filter_prune (info, plist);
+  return (plist->size != 0) ? abfd : NULL;
+}
+
+bfd *
+_bfd_elf_link_setup_build_attributes (struct bfd_link_info *info)
+{
+  obj_attr_subsection_list *frozen_ =
+    &elf_obj_attr_subsections (info->output_bfd);
+
+  bfd_search_result_t res =
+    bfd_linear_search_one_with_build_attributes (info);
+
+  /* If res.pbfd is NULL, it means that it didn't find any ELF object files.  */
+  if (res.pbfd == NULL)
+    return NULL;
+
+  /* If the input has GNU properties, try to convert the relevant ones to object
+     attributes.  */
+  if (elf_properties (res.pbfd) != NULL)
+    {
+      _bfd_elf_translate_relevant_gnu_props_to_obj_attrs (res.pbfd);
+      if (elf_obj_attr_subsections (res.pbfd).size > 0)
+	res.has_build_attributes = true;
+    }
+
+  /* No frozen object attributes and no object file, so nothing to do.  */
+  if (!res.has_build_attributes && frozen_->size == 0)
+    return NULL;
+  /* If frozen object attributes were set by some command-line options, we still
+     need to emit warnings / errors if there are incompatibilities.  */
+
+  if (res.sec == NULL)
+    res.sec = create_build_attributes_section (info, res.pbfd);
+
+  /* Sort the frozen subsections and attributes in case that they were not
+     inserted in the correct order.  */
+  obj_attr_subsection_v2_sort (frozen_);
+
+  bool success = true;
+
+  /* Merge duplicates subsections and attributes.  */
+  success &= obj_attr_subsection_v2_file_scope_merge (res.pbfd);
+
+  /* Translate object attributes from res.pbfd to GNU properties if they have an
+     equivalence, before distorting them with the merge.  */
+  _bfd_elf_translate_relevant_obj_attrs_to_gnu_props (res.pbfd);
+
+  /* Emit warnings / errors (if any) when merging res.pbfd against frozen.
+     res.pbfd will be used as the ref and will be accumulating the merge result.
+     It means that we will lose its information.  */
+  success &= obj_attr_subsection_v2_merge_frozen (info, res.pbfd,
+    frozen_->first_);
+
+  /* Merge build attributes sections.  */
+  info->callbacks->minfo (_("\n"));
+  info->callbacks->minfo (_("Merging build attributes\n"));
+  info->callbacks->minfo (_("\n"));
+
+  res.pbfd = bfd_elf_merge_build_attributes (info, res.pbfd);
+  success &= (res.pbfd != NULL);
+  res.pbfd = bfd_elf_prune_build_attributes (info, res.pbfd);
+
+  if (res.pbfd && elf_obj_attr_subsections (res.pbfd).size > 0)
+    {
+      /* Shallow-copy the build attributes into output_bfd.  */
+      elf_obj_attr_subsections (info->output_bfd) =
+	elf_obj_attr_subsections (res.pbfd);
+
+      /* Note: the build attributes section in the output object is copied from
+	 the input object which was used for the merge (res.pbfd).  No need to
+	 create it here.  However, so that the section is copied to the output
+	 object, the size must be different from 0.  For now, we will set this
+	 size to 1.  The real size will be set later.  */
+      res.sec->size = 1;
+    }
+
+  return success ? res.pbfd : NULL;
+}
+
 /* Allocate/find an object attribute.  */
 static obj_attribute *
 elf_new_obj_attr (bfd *abfd, int vendor, unsigned int tag)
@@ -1003,6 +2396,32 @@ bfd_elf_parse_attr_section_v2 (bfd *abfd,
     }
 }
 
+/* Translate the relevant GNU properties to object attributes v2.  */
+void
+_bfd_elf_translate_relevant_gnu_props_to_obj_attrs (bfd *abfd)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (abfd);
+  if (be->translate_relevant_gnu_props_to_obj_attrs == NULL)
+    return;
+
+  for (elf_property_list *p = elf_properties (abfd); p != NULL; p = p->next)
+    be->translate_relevant_gnu_props_to_obj_attrs (abfd, p);
+}
+
+/* Translate the relevant object attributes v2 to GNU properties.  */
+void
+_bfd_elf_translate_relevant_obj_attrs_to_gnu_props (bfd *abfd)
+{
+  const struct elf_backend_data *be = get_elf_backend_data (abfd);
+  if (be->translate_relevant_obj_attrs_to_gnu_props == NULL)
+    return;
+
+  for (obj_attr_subsection_v2 *subsec = elf_obj_attr_subsections (abfd).first_;
+       subsec != NULL;
+       subsec = subsec->next)
+    be->translate_relevant_obj_attrs_to_gnu_props (abfd, subsec);
+}
+
 /* Parse an object attributes section.
    Note: The parsing setup is common between object attributes v1 and v2.  */
 void
@@ -1224,6 +2643,7 @@ _bfd_elf_obj_attr_v2_init (unsigned int tag,
   memset ((void *) attr, 0, sizeof (obj_attr_v2));
   attr->tag = tag;
   attr->vals = vals;
+  attr->status = obj_attr_v2_ok;
   return attr;
 }
 
@@ -1249,6 +2669,7 @@ _bfd_elf_obj_attr_v2_copy (obj_attr_v2 *other,
     vals.uint_val = other->vals.uint_val;
 
   obj_attr_v2 *copy =_bfd_elf_obj_attr_v2_init (other->tag, vals);
+  copy->status = other->status;
   return copy;
 }
 
@@ -1318,6 +2739,7 @@ _bfd_elf_obj_attr_subsection_v2_init (const char* name,
   subsection->scope = scope;
   subsection->optional = optional;
   subsection->encoding = encoding;
+  subsection->status = obj_attr_subsection_v2_ok;
   return subsection;
 }
 
@@ -1335,6 +2757,8 @@ _bfd_elf_obj_attr_subsection_v2_copy (obj_attr_subsection_v2 const *other)
   obj_attr_subsection_v2* new_subsec =
     _bfd_elf_obj_attr_subsection_v2_init (other->name, other->scope,
 					  other->optional, other->encoding);
+  new_subsec->status = other->status;
+
   for (obj_attr_v2* attr = other->first_;
        attr != NULL;
        attr = attr->next)
diff --git a/bfd/elf-attrs.h b/bfd/elf-attrs.h
index a4af0da1f68..628638cc847 100644
--- a/bfd/elf-attrs.h
+++ b/bfd/elf-attrs.h
@@ -33,11 +33,24 @@ typedef enum obj_attr_encoding_v2
   NTBS    = 1,
 } obj_attr_encoding_v2;
 
+extern const char *
+obj_attr_encoding_v2_to_string (obj_attr_encoding_v2);
+
 typedef union obj_attr_value_v2 {
   uint32_t uint_val;
   const char* string_val;
 } obj_attr_value_v2;
 
+typedef enum obj_attr_v2_status
+{
+  /* An attribute that is unknown to the linker, and so cannot be merged.  */
+  obj_attr_v2_unknown = 0,
+  /* An attribute that was reported as corrupted.  */
+  obj_attr_v2_corrupted,
+  /* A valid attribute.  */
+  obj_attr_v2_ok,
+} obj_attr_v2_status;
+
 typedef struct obj_attr_v2 {
   /* The name/tag of an attribute.  */
   uint32_t tag;
@@ -45,6 +58,9 @@ typedef struct obj_attr_v2 {
   /* The value assigned to an attribute, can be ULEB128 or NTBS.  */
   union obj_attr_value_v2 vals;
 
+  /* The attribute status after merge.  */
+  obj_attr_v2_status status;
+
   /* The next attribute in the list or NULL.  */
   struct obj_attr_v2 *next;
 
@@ -59,6 +75,16 @@ typedef enum obj_attr_subsection_scope_v2
   SUBSEC_PRIVATE = 1,
 } obj_attr_subsection_scope_v2;
 
+typedef enum obj_attr_subsection_v2_status
+{
+  /* A subsection that is unknown to the linker, and so cannot be merged.  */
+  obj_attr_subsection_v2_unknown = 0,
+  /* A subsection that was reported as corrupted.  */
+  obj_attr_subsection_v2_corrupted,
+  /* A valid subsection.  */
+  obj_attr_subsection_v2_ok,
+} obj_attr_subsection_v2_status;
+
 typedef struct obj_attr_subsection_v2 {
   /* The name of the subsection.  */
   const char *name;
@@ -87,8 +113,14 @@ typedef struct obj_attr_subsection_v2 {
   /* The size of the list.  */
   uint32_t size;
 
+  /* The subsection status after merge.  */
+  obj_attr_subsection_v2_status status;
+
 } obj_attr_subsection_v2;
 
+extern const char *
+obj_attr_subsection_v2_optional_to_string (bool);
+
 typedef struct obj_attr_subsection_list
 {
   /* A pointer to the first node of the list.  */
@@ -118,3 +150,47 @@ typedef struct
   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 known_tag_v2 *
+known_obj_attr_v2_find_by_tag (const struct elf_backend_data *,
+  const char*, uint32_t);
+
+extern const char *
+obj_attr_v2_tag_to_string (const struct elf_backend_data *, const char*,
+  uint32_t);
+
+enum obj_attr_v2_merge_result_reason
+{
+  /* Default: everything is ok.  */
+  NONE = 0,
+  /* The result value of the merge is the same as REF.  */
+  SAME_VALUE_AS_REF,
+  /* No implementation of a merge for this attribute exists.  */
+  UNSUPPORTED,
+  /* The merge failed, an error message should be logged.  */
+  ERROR,
+};
+typedef struct {
+  /* Should the merge be performed ?  */
+  bool merge;
+  /* The merged value.  */
+  union obj_attr_value_v2 vals;
+  /* If the merge should not be performed, give the reason to differentiate
+     error cases from normal cases.  Typically, if REF already is set to the
+     same value as the merged result, no merge is needed, and this is not an
+     error.  */
+  enum obj_attr_v2_merge_result_reason reason;
+} obj_attr_v2_merge_result;
+
+/* Re-usable merge policies.*/
+/* For now, only AND-merge is used by AArch64 backend. Additional policies
+   (Integer-OR, String-ADD) are part of the GNU testing namespace. If they
+   appear to be usefull for a backend at some point, they should be exposed
+   to the backend here below.  */
+extern obj_attr_v2_merge_result
+obj_attr_v2_tag_merge_AND (struct bfd_link_info *, bfd *, obj_attr_subsection_v2 *,
+  obj_attr_v2 *, obj_attr_v2 *, obj_attr_v2 *);
diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 3e825299a56..e8e8428c8e4 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -1641,6 +1641,31 @@ struct elf_backend_data
   /* The version of object attributes (1 or 2).  */
   unsigned int obj_attrs_version;
 
+  /* 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;
+
+  /* Translate the relevant GNU properties to object attributes v2.  */
+  void (*translate_relevant_gnu_props_to_obj_attrs) (bfd *,
+    elf_property_list *);
+
+  /* Translate the relevant object attributes v2 to GNU properties.  */
+  void (*translate_relevant_obj_attrs_to_gnu_props) (bfd *,
+    obj_attr_subsection_v2 *);
+
+  /* Check build attributes subsection v2 against expected properties.  */
+  bool (*obj_attr_subsection_v2_match_known) (struct bfd_link_info *, bfd *,
+    obj_attr_subsection_v2 *);
+
+  /* Get default value for an attribute.  */
+  bool (*obj_attr_v2_default_value) (struct bfd_link_info *,
+    const known_tag_v2 *, obj_attr_subsection_v2 *, obj_attr_v2 *);
+
+  /* Merge a build attribute v2.  */
+  obj_attr_v2_merge_result (*obj_attr_v2_tag_merge) (struct bfd_link_info *,
+    bfd *, obj_attr_subsection_v2 *, obj_attr_v2 *, obj_attr_v2 *, obj_attr_v2 *);
+
   /* 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
@@ -1662,6 +1687,9 @@ struct elf_backend_data
   bool (*merge_gnu_properties) (struct bfd_link_info *, bfd *, bfd *,
 				       elf_property *, elf_property *);
 
+  /* Set up build attributes.  */
+  bfd *(*setup_build_attributes) (struct bfd_link_info *);
+
   /* Set up GNU properties.  */
   bfd *(*setup_gnu_properties) (struct bfd_link_info *);
 
@@ -3083,9 +3111,14 @@ extern obj_attribute *bfd_elf_add_obj_attr_int_string
 
 extern bool _bfd_elf_write_section_build_attributes
   (bfd *, struct bfd_link_info *);
+extern bfd *_bfd_elf_link_setup_build_attributes
+  (struct bfd_link_info *);
+
 extern char *_bfd_elf_attr_strdup (bfd *, const char *);
 extern void _bfd_elf_copy_obj_attributes (bfd *, bfd *);
 extern int _bfd_elf_obj_attrs_arg_type (bfd *, int, unsigned int);
+extern void _bfd_elf_translate_relevant_gnu_props_to_obj_attrs (bfd *);
+extern void _bfd_elf_translate_relevant_obj_attrs_to_gnu_props (bfd *);
 extern void _bfd_elf_parse_attributes (bfd *, Elf_Internal_Shdr *);
 extern bool _bfd_elf_merge_object_attributes
   (bfd *, struct bfd_link_info *);
diff --git a/bfd/elf.c b/bfd/elf.c
index ee89dd909da..40c252b36ed 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -11447,17 +11447,22 @@ elfobj_grok_gnu_build_id (bfd *abfd, Elf_Internal_Note *note)
 static bool
 elfobj_grok_gnu_note (bfd *abfd, Elf_Internal_Note *note)
 {
+  bool res;
   switch (note->type)
     {
-    default:
-      return true;
-
     case NT_GNU_PROPERTY_TYPE_0:
-      return _bfd_elf_parse_gnu_properties (abfd, note);
+      res = _bfd_elf_parse_gnu_properties (abfd, note);
+      break;
 
     case NT_GNU_BUILD_ID:
-      return elfobj_grok_gnu_build_id (abfd, note);
+      res = elfobj_grok_gnu_build_id (abfd, note);
+      break;
+
+    default:
+      res = true;
+      break;
     }
+  return res;
 }
 
 static bool
diff --git a/bfd/elfxx-target.h b/bfd/elfxx-target.h
index cff508dbbce..1fffc9194c9 100644
--- a/bfd/elfxx-target.h
+++ b/bfd/elfxx-target.h
@@ -559,6 +559,27 @@
 #ifndef elf_backend_obj_attrs_version
 #define elf_backend_obj_attrs_version		1 /* Default version.  */
 #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_translate_relevant_gnu_props_to_obj_attrs
+#define elf_backend_translate_relevant_gnu_props_to_obj_attrs	NULL
+#endif
+#ifndef elf_backend_translate_relevant_obj_attrs_to_gnu_props
+#define elf_backend_translate_relevant_obj_attrs_to_gnu_props	NULL
+#endif
+#ifndef elf_backend_obj_attr_subsection_v2_match_known
+#define elf_backend_obj_attr_subsection_v2_match_known	NULL
+#endif
+#ifndef elf_backend_obj_attr_v2_default_value
+#define elf_backend_obj_attr_v2_default_value	NULL
+#endif
+#ifndef elf_backend_obj_attr_v2_tag_merge
+#define elf_backend_obj_attr_v2_tag_merge	NULL
+#endif
 #ifndef elf_backend_obj_attrs_order
 #define elf_backend_obj_attrs_order		NULL
 #endif
@@ -571,6 +592,9 @@
 #ifndef elf_backend_merge_gnu_properties
 #define elf_backend_merge_gnu_properties	NULL
 #endif
+#ifndef elf_backend_setup_build_attributes
+#define elf_backend_setup_build_attributes	_bfd_elf_link_setup_build_attributes
+#endif
 #ifndef elf_backend_setup_gnu_properties
 #define elf_backend_setup_gnu_properties	_bfd_elf_link_setup_gnu_properties
 #endif
@@ -938,10 +962,18 @@ static const struct elf_backend_data elfNN_bed =
   elf_backend_obj_attrs_arg_type,
   elf_backend_obj_attrs_section_type,
   elf_backend_obj_attrs_version,
+  elf_backend_obj_attr_v2_known_subsections,
+  elf_backend_obj_attr_v2_known_subsections_size,
+  elf_backend_translate_relevant_gnu_props_to_obj_attrs,
+  elf_backend_translate_relevant_obj_attrs_to_gnu_props,
+  elf_backend_obj_attr_subsection_v2_match_known,
+  elf_backend_obj_attr_v2_default_value,
+  elf_backend_obj_attr_v2_tag_merge,
   elf_backend_obj_attrs_order,
   elf_backend_obj_attrs_handle_unknown,
   elf_backend_parse_gnu_properties,
   elf_backend_merge_gnu_properties,
+  elf_backend_setup_build_attributes,
   elf_backend_setup_gnu_properties,
   elf_backend_fixup_gnu_properties,
   elf_backend_compact_eh_encoding,
diff --git a/ld/ldelf.c b/ld/ldelf.c
index f4f27fc3873..e16899db9b8 100644
--- a/ld/ldelf.c
+++ b/ld/ldelf.c
@@ -1291,6 +1291,7 @@ ldelf_after_open (int use_libpath, int native, int is_linux, int is_freebsd,
 	}
     }
 
+  get_elf_backend_data (link_info.output_bfd)->setup_build_attributes (&link_info);
   get_elf_backend_data (link_info.output_bfd)->setup_gnu_properties (&link_info);
 
   /* Do not allow executable files to be used as inputs to the link.  */
-- 
2.49.0



More information about the Binutils mailing list