[PATCH v5 08/20] readelf: dump Object Attributes v2

Matthieu Longo matthieu.longo@arm.com
Mon Jul 7 16:49:25 GMT 2025


From: Richard Ball <richard.ball@arm.com>

Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
---
 binutils/readelf.c | 317 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 317 insertions(+)

diff --git a/binutils/readelf.c b/binutils/readelf.c
index 573024727f2..afefdc388fe 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -58,6 +58,7 @@
 #define BFD64
 
 #include "bfd.h"
+#include "elf-attrs.h"
 #include "bucomm.h"
 #include "elfcomm.h"
 #include "demanguse.h"
@@ -19430,6 +19431,318 @@ free_data:
   return res;
 }
 
+typedef gas_symbol_t known_tag_v2;
+typedef struct
+{
+  const char *subsec_name;
+  const known_tag_v2 *known_tags;
+  const size_t len;
+} _known_subsection_v2;
+
+static const known_tag_v2 known_tags_aeabi_feature_and_bits [] =
+{
+  {"Tag_Feature_BTI", .value = {
+    .val.u32 = Tag_Feature_BTI,
+    .vtype = VALUE_U32
+  }},
+  {"Tag_Feature_PAC", .value = {
+    .val.u32 = Tag_Feature_PAC,
+    .vtype = VALUE_U32
+  }},
+  {"Tag_Feature_GCS", .value = {
+    .val.u32 = Tag_Feature_GCS,
+    .vtype = VALUE_U32
+  }},
+};
+static const known_tag_v2 known_tags_aeabi_pauthabi [] =
+{
+  {"Tag_PAuth_Platform", .value = {
+    .val.u32 = Tag_PAuth_Platform,
+    .vtype = VALUE_U32
+  }},
+  {"Tag_PAuth_Schema", .value = {
+    .val.u32 = Tag_PAuth_Schema,
+    .vtype = VALUE_U32
+  }},
+};
+static const _known_subsection_v2 known_subsections[] =
+{
+  {
+    .subsec_name = "aeabi_feature_and_bits",
+    .known_tags = known_tags_aeabi_feature_and_bits,
+    .len = ARRAY_SIZE (known_tags_aeabi_feature_and_bits),
+  },
+  {
+    .subsec_name = "aeabi_pauthabi",
+    .known_tags = known_tags_aeabi_pauthabi,
+    .len = ARRAY_SIZE (known_tags_aeabi_pauthabi),
+  },
+};
+
+static const _known_subsection_v2 *
+_identify_subsection (const char *name)
+{
+  for (unsigned i = 0; i < ARRAY_SIZE (known_subsections); ++i)
+    if (strcmp (name, known_subsections[i].subsec_name) == 0)
+      return &known_subsections[i];
+  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->value.val.u32 == tag)
+	return known_tag;
+    }
+  return NULL;
+}
+
+static unsigned char *
+attr_v2_default_value_display (unsigned char *cursor,
+			       const unsigned char *const end,
+			       obj_attr_encoding_v2 value_encoding)
+{
+  switch (value_encoding)
+    {
+    case OA_ENC_NTBS:
+      cursor = display_tag_value (-1, cursor, end);
+      break;
+    case OA_ENC_ULEB128:
+      cursor = display_tag_value (0, cursor, end);
+      break;
+    case OA_ENC_UNSET:
+      abort ();
+    }
+  return cursor;
+}
+
+static unsigned char *
+display_aarch64_attribute (unsigned char *cursor,
+			   const unsigned char *const end,
+			   const known_tag_v2 *tag_info,
+			   obj_attr_encoding_v2 value_encoding)
+{
+  printf ("    %s:	", tag_info->identifier);
+  /* FIXME: implement a different display for PAuthABI values.  */
+  return attr_v2_default_value_display (cursor, end, value_encoding);
+}
+
+typedef unsigned char *(*display_arch_attr_t) (unsigned char *,
+					       const unsigned char *const,
+					       const known_tag_v2 *,
+					       obj_attr_encoding_v2);
+
+static unsigned char *
+display_attr_v2 (unsigned char *cursor,
+		 const unsigned char *const end,
+		 const char *subsec_name,
+		 obj_attr_encoding_v2 value_encoding,
+		 display_arch_attr_t display_arch_attr)
+{
+  uint32_t tag;
+  READ_ULEB (tag, cursor, end);
+
+  const known_tag_v2 *tag_info = NULL;
+  const _known_subsection_v2 *subsec_info = _identify_subsection (subsec_name);
+  if (subsec_info != NULL)
+    tag_info = identify_tag (subsec_info, tag);
+
+  if (tag_info != NULL)
+    return display_arch_attr (cursor, end, tag_info, value_encoding);
+
+  printf ("    Tag_unknown_%u:	", tag);
+  return attr_v2_default_value_display (cursor, end, value_encoding);
+}
+
+typedef struct {
+  bool err;
+  uint64_t read;
+} BufferReadOp_t;
+
+static BufferReadOp_t
+elf_parse_attrs_subsection_v2 (unsigned char *cursor,
+			       const uint64_t max_read,
+			       const char *public_name,
+			       display_arch_attr_t display_arch_attr)
+{
+  BufferReadOp_t op = { .err = false, .read = 0 };
+
+  const uint32_t F_SUBSECTION_LEN = sizeof (uint32_t);
+  /* The minimum subsection length is 5: 4 bytes for the length itself, and 1
+     byte for an empty NUL-terminated string, and no vendor-data.  */
+  const uint32_t F_MIN_SUBSECTION_DATA_LEN = F_SUBSECTION_LEN + 1;
+
+  /* Handle cases where the attributes data is not strictly valid (e.g. due to
+     fuzzing).  */
+  if (max_read <= F_SUBSECTION_LEN)
+    {
+      error (_("Build attributes section ends prematurely\n"));
+      return op;
+    }
+  uint32_t subsection_len = byte_get (cursor, F_SUBSECTION_LEN);
+  op.read += F_SUBSECTION_LEN;
+  cursor += F_SUBSECTION_LEN;
+  if (subsection_len > max_read)
+    {
+      error (_("Bad subsection length (%u > max=%lu)\n"),
+	     subsection_len, max_read);
+      /* Error, but still try to display the content until meeting a more
+	 serious error.  */
+      subsection_len = max_read;
+      op.err = true;
+    }
+  else if (subsection_len < F_MIN_SUBSECTION_DATA_LEN)
+    {
+      error (_("Subsection length of %u is too small\n"), subsection_len);
+      op.err = true;
+      return op;
+    }
+
+  size_t subsection_name_len = strnlen ((char *) cursor, subsection_len) + 1;
+  if (subsection_name_len >= subsection_len)
+    {
+      error (_("Subsection name seems corrupted (missing '\\0')\n"));
+      op.err = true;
+      return op;
+    }
+  /* If subsection_name_len == 1, i.e. the subsection name is '\0', we still
+     consider the name valid for dumping, and an empty string will be displayed.
+     However, in practice, such a name would be unexploitable by the linker
+     during the merge, thus the subsection would be dropped.  */
+
+  unsigned char *const end = cursor + subsection_len - F_SUBSECTION_LEN;
+  while (cursor < end)
+    {
+      const char *subsec_name = (const char *) cursor;
+      printf (_(" - Name:	  %s\n"), subsec_name);
+      bool public_subsection =
+	strncmp (subsec_name, public_name, strlen (public_name)) == 0;
+      cursor += subsection_name_len;
+      op.read += subsection_name_len;
+
+      printf ("   Scope:	  %s\n",
+	      public_subsection ? "public" : "private");
+      printf ("   Length:	  %u\n", subsection_len);
+
+      uint8_t optional;
+      READ_ULEB (optional, cursor, end);
+      op.read += 1;
+
+      if (optional > 1)
+	{
+	  error (_("Optional value seems corrupted, got %u but only"
+		   " 0x0 (false) or 0x1 (true) are valid values."), optional);
+	  op.err = true;
+	  op.read = subsection_len;
+	  return op;
+	}
+
+      printf ("   Comprehension: %s\n", optional ? "optional" : "required");
+
+      uint8_t value_encoding_raw;
+      READ_ULEB (value_encoding_raw, cursor, end);
+      op.read += 1;
+      enum obj_attr_encoding_v2 value_encoding
+	= obj_attr_encoding_v2_from_u8 (value_encoding_raw);
+
+      if (value_encoding > OA_ENC_MAX)
+	{
+	  error (_("Attribute type seems corrupted, got %u but only 0x0 (ULEB128)"
+		   " or 0x1 (NTBS) are valid types."), value_encoding_raw);
+	  op.err = true;
+	  op.read = subsection_len;
+	  return op;
+	}
+
+      switch (value_encoding)
+	{
+	case OA_ENC_ULEB128:
+	  printf ("   Encoding:	  ULEB128\n");
+	  break;
+	case OA_ENC_NTBS:
+	  printf ("   Encoding:	  NTBS\n");
+	  break;
+	default:
+	  abort ();
+	}
+
+      printf ("   Values:\n");
+      while (cursor < end)
+	{
+	  unsigned char *cursor_new =
+	    display_attr_v2 (cursor, end, subsec_name, value_encoding,
+			     display_arch_attr);
+	  op.read += (cursor_new - cursor);
+	  cursor = cursor_new;
+	}
+      putchar ('\n');
+    }
+
+  if (cursor != end)
+    abort ();
+
+  return op;
+}
+
+static bool
+process_attributes_v2 (Filedata *filedata,
+		       const char *public_name,
+		       uint32_t section_type,
+		       display_arch_attr_t display_arch_attr)
+{
+  /* Find the section header so that we get the size.  */
+  Elf_Internal_Shdr *sec_hdr = find_section_by_type (filedata, section_type);
+  if (sec_hdr == NULL)
+    /* No section, exit without error.  */
+    return true;
+
+  unsigned char *const data = (unsigned char *)
+    get_data (NULL, filedata, sec_hdr->sh_offset, 1, sec_hdr->sh_size,
+	      _("build attributes"));
+  if (data == NULL)
+    return false;
+
+  unsigned char *cursor = data;
+  bool res = true;
+
+  /* The first character is the version of the attributes.
+     Currently only version 1, (aka 'A') is recognised here.  */
+  if (*cursor != 'A')
+    {
+      error (_("Unknown attributes version '%c'(0x%02x) - expecting 'A'\n"),
+	     ISPRINT(*cursor) ? *cursor : '?', *cursor);
+      res = false;
+      goto free_data;
+    }
+
+  ++cursor;
+
+  printf (("Subsections:\n"));
+  BufferReadOp_t op;
+  for (uint64_t remaining = sec_hdr->sh_size - 1; // already read 'A'
+       remaining > 1;
+       remaining -= op.read, cursor += op.read)
+    {
+      op = elf_parse_attrs_subsection_v2 (cursor, remaining, public_name,
+					  display_arch_attr);
+      if (op.err)
+	{
+	  error (_("Cannot parse subsection at offset %lx"),
+	    sec_hdr->sh_size - remaining);
+	  res = false;
+	  goto free_data;
+	}
+    }
+
+free_data:
+  free (data);
+
+  return res;
+}
+
 /* DATA points to the contents of a MIPS GOT that starts at VMA PLTGOT.
    Print the Address, Access and Initial fields of an entry at VMA ADDR
    and return the VMA of the next entry, or -1 if there was a problem.
@@ -23457,6 +23770,10 @@ process_arch_specific (Filedata * filedata)
 				 display_arm_attribute,
 				 display_generic_attribute);
 
+    case EM_AARCH64:
+      return process_attributes_v2 (filedata, "aeabi", SHT_AARCH64_ATTRIBUTES,
+				    display_aarch64_attribute);
+
     case EM_MIPS:
     case EM_MIPS_RS3_LE:
       return process_mips_specific (filedata);
-- 
2.50.0



More information about the Binutils mailing list