[v0,12/15] ld's BAs merge saga, step one: generic merge works on its own without backend support

Message ID 20250310175131.1217374-13-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
  - setup_build_attributes called before setup_gnu_properties.
- take into account relevant GNU properties in inputs (if
  implemented by the backend) when parsing the GNU property
  notes, and pre-populate the object attributes.
- translate BAs to GNU properties (if implemented by the backend)
  as further compatibility checks against shared objects can be
  performed by the linker. Additionally, the runtime linker does
  not understand BAs yet, so a translation of some BAs is required.
---
 bfd/elf-attrs.c    | 1249 ++++++++++++++++++++++++++++++++++++++++++++
 bfd/elf-attrs.h    |   76 +++
 bfd/elf-bfd.h      |   33 ++
 bfd/elf.c          |   22 +-
 bfd/elfxx-target.h |   32 ++
 ld/ldelf.c         |    1 +
 6 files changed, 1408 insertions(+), 5 deletions(-)
  

Patch

diff --git a/bfd/elf-attrs.c b/bfd/elf-attrs.c
index 49c301326f2..02bfd7ab594 100644
--- a/bfd/elf-attrs.c
+++ b/bfd/elf-attrs.c
@@ -382,6 +382,1227 @@  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;
+}
+
+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))
+      {
+	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;
+
+  /* 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_);
+
+  /* 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.  */
+  bool success = true;
+  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)
@@ -1097,6 +2318,32 @@  bfd_elf_parse_attr_section_v2 (bfd *abfd,
   obj_attr_subsection_v2_sort (subsecs);
 }
 
+/* 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
@@ -1318,6 +2565,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;
 }
 
@@ -1413,6 +2661,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;
 }
 
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 3f8bc838bfb..03c29bf6e51 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -11446,17 +11446,29 @@  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);
+      if (res)
+	{
+	  uint32_t version = get_elf_backend_data (abfd)->obj_attrs_version;
+
+	  if (version == 2)
+	    _bfd_elf_translate_relevant_gnu_props_to_obj_attrs (abfd);
+	}
+      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.  */