]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
readelf: dump Object Attributes v2
authorRichard Ball <richard.ball@arm.com>
Sat, 12 Apr 2025 17:24:40 +0000 (18:24 +0100)
committerMatthieu Longo <matthieu.longo@arm.com>
Thu, 22 Jan 2026 10:11:16 +0000 (10:11 +0000)
This patch adds support to readelf for displaying Object Attributes v2
on AArch64. The list of known tags and subsections match the ones in
the document "Build Attributes for the Arm 64-bit Architecture (AArch64)"
available at [1].

[1]: https://github.com/ARM-software/abi-aa

Co-Authored-By: Matthieu Longo <matthieu.longo@arm.com>
binutils/readelf.c

index d40d13635004d8110af8b2cc9ccaa61ce522b656..6d0c211fb1b644de80a8fbf9b49293e11c7d0e28 100644 (file)
@@ -58,6 +58,7 @@
 #define BFD64
 
 #include "bfd.h"
+#include "elf-attrs.h"
 #include "bucomm.h"
 #include "elfcomm.h"
 #include "demanguse.h"
@@ -19976,12 +19977,353 @@ process_attributes (Filedata * filedata,
        }
     }
 
-free_data:
+ free_data:
   free (contents);
 
   return res;
 }
 
+typedef obj_attr_tag_info_t oav2_known_tag_t;
+
+typedef struct
+{
+  const char *subsec_name;
+  const oav2_known_tag_t *known_tags;
+  size_t len;
+} oav2_known_subsection_t;
+
+static const oav2_known_tag_t known_tags_aeabi_feature_and_bits[] =
+{
+  {"Tag_Feature_BTI", Tag_Feature_BTI},
+  {"Tag_Feature_PAC", Tag_Feature_PAC},
+  {"Tag_Feature_GCS", Tag_Feature_GCS},
+};
+
+static const oav2_known_tag_t known_tags_aeabi_pauthabi[] =
+{
+  {"Tag_PAuth_Platform", Tag_PAuth_Platform},
+  {"Tag_PAuth_Schema",   Tag_PAuth_Schema},
+};
+static const oav2_known_subsection_t 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 oav2_known_subsection_t *
+oav2_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 oav2_known_tag_t *
+oav2_identify_tag (const oav2_known_subsection_t *subsec, obj_attr_tag_t tag)
+{
+  for (unsigned i = 0; i < subsec->len; ++i)
+    {
+      const oav2_known_tag_t *known_tag = &subsec->known_tags[i];
+      if (known_tag->value == tag)
+       return known_tag;
+    }
+  return NULL;
+}
+
+static const unsigned char *
+oav2_display_attr_value (const unsigned char *cursor,
+                        const unsigned char *const end,
+                        obj_attr_encoding_v2_t 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;
+}
+
+/* Print out the raw attribute value.  It should be feasible to support custom
+   formatters here for known tags that explain the interpretation of specific
+   values.  */
+static const unsigned char *
+display_aarch64_attribute (const unsigned char *cursor,
+                          const unsigned char *const end,
+                          const oav2_known_tag_t *tag_info,
+                          obj_attr_encoding_v2_t value_encoding)
+{
+  printf ("    %s:     ", tag_info->name);
+  return oav2_display_attr_value (cursor, end, value_encoding);
+}
+
+typedef const unsigned char *(*display_arch_attr_t)
+  (const unsigned char *,
+   const unsigned char *const,
+   const oav2_known_tag_t *,
+   obj_attr_encoding_v2_t);
+
+static const unsigned char *
+display_attr_v2 (const unsigned char *cursor,
+                const unsigned char *const end,
+                const oav2_known_subsection_t *subsec_info,
+                obj_attr_encoding_v2_t value_encoding,
+                display_arch_attr_t display_arch_attr)
+{
+  obj_attr_tag_t tag;
+  READ_ULEB (tag, cursor, end);
+
+  const oav2_known_tag_t *tag_info = NULL;
+  if (subsec_info != NULL)
+    tag_info = oav2_identify_tag (subsec_info, tag);
+
+  if (tag_info != NULL)
+    return display_arch_attr (cursor, end, tag_info, value_encoding);
+
+  printf ("    Tag_unknown_%"PRIu64":  ", tag);
+  return oav2_display_attr_value (cursor, end, value_encoding);
+}
+
+#define READ_SUBSEC_PROPERTY(var, read_len, start, end)                                \
+do                                                                             \
+  {                                                                            \
+    if (start >= end)                                                          \
+      error                                                                    \
+       (_("end of data encountered whilst reading property of subsection\n")); \
+    var = byte_get (start, read_len);                                          \
+    start += read_len;                                                         \
+  }                                                                            \
+while (0)
+
+typedef struct {
+  bool err;
+  uint64_t read;
+} BufferReadOp_t;
+
+static BufferReadOp_t
+elf_parse_attrs_subsection_v2 (const 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);
+  const uint32_t F_SUBSECTION_COMPREHENSION = sizeof(uint8_t);
+  const uint32_t F_SUBSECTION_ENCODING = sizeof(uint8_t);
+  /* The minimum subsection length is 7: 4 bytes for the length itself, and 1
+     byte for an empty NUL-terminated string, 1 byte for the comprehension,
+     1 byte for the encoding, and no vendor-data.  */
+  const uint32_t F_MIN_SUBSECTION_DATA_LEN
+    = F_SUBSECTION_LEN + 1 /* for '\0' */
+      + F_SUBSECTION_COMPREHENSION + F_SUBSECTION_ENCODING;
+
+  /* Handle cases where the attributes data is not strictly valid (e.g. due to
+     fuzzing).  */
+  if (max_read < F_MIN_SUBSECTION_DATA_LEN)
+    {
+      error (_("Object attributes section ends prematurely\n"));
+      return op;
+    }
+
+  unsigned int subsection_len = byte_get (cursor, F_SUBSECTION_LEN);
+  cursor += F_SUBSECTION_LEN;
+  op.read += F_SUBSECTION_LEN;
+  if (subsection_len > max_read)
+    {
+      error (_("Bad subsection length: too big (%u > max=%"PRIu64")\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 (_("Bad subsection length: too small (%u < min=%"PRIu32")\n"),
+            subsection_len, F_MIN_SUBSECTION_DATA_LEN);
+      /* Error, but still try to display the content until meeting a more
+        serious error.  */
+      subsection_len = max_read;
+      op.err = true;
+    }
+
+  const size_t MAX_SUBSECTION_NAME_LEN
+    = subsection_len - F_SUBSECTION_LEN
+      - F_SUBSECTION_COMPREHENSION - F_SUBSECTION_ENCODING;
+  size_t subsection_name_len
+    = strnlen ((char *) cursor, MAX_SUBSECTION_NAME_LEN);
+  if (subsection_name_len >= MAX_SUBSECTION_NAME_LEN)
+    {
+      error (_("Subsection name seems corrupted (missing '\\0')\n"));
+      op.err = true;
+      return op;
+    }
+  /* Note: if the length of the subsection name is 0 (i.e. the string is '\0'),
+     it is still considered a valid name 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.  */
+  subsection_name_len += 1;
+
+  /* Note: at this stage,
+     1. the length of the subsection name is validated, as the presence of '\0'
+       at the end of the string, so no risk of buffer overrun.
+     2. the data for comprehension and encoding can also safely be read.  */
+  const 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);
+      /* The code below needs to be kept in sync with the code of
+        bfd_elf_obj_attr_subsection_v2_scope() in bfd/elf-attrs.c.  */
+      size_t public_name_len = strlen (public_name);
+      bool public_subsection
+       = strncmp (subsec_name, public_name, public_name_len) == 0
+         && subsec_name[public_name_len] == '_';
+      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_SUBSEC_PROPERTY (optional, sizeof (optional), cursor, end);
+      op.read += sizeof (optional);
+
+      if (optional > 1)
+       {
+         error (_("Optional value seems corrupted, got %d but only"
+                  " 0 (false) or 1 (true) are valid values\n"),
+                optional);
+         op.err = true;
+         op.read = subsection_len;
+         return op;
+       }
+
+      printf (_("   Comprehension: %s\n"), optional ? "optional" : "required");
+
+      uint8_t value_encoding_raw;
+      READ_SUBSEC_PROPERTY (value_encoding_raw, sizeof (value_encoding_raw),
+                           cursor, end);
+      op.read += sizeof (value_encoding_raw);
+
+      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 %d but only 0 (ULEB128)"
+                  " or 1 (NTBS) are valid types\n"),
+                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 ();
+       }
+
+      const oav2_known_subsection_t *subsec_info
+       = oav2_identify_subsection (subsec_name);
+      printf (_("   Values:\n"));
+      while (cursor < end)
+       {
+         const unsigned char *cursor_new
+           = display_attr_v2 (cursor, end, subsec_info, 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,
+             _("object 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 %"PRIx64"\n"),
+           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.
@@ -24247,6 +24589,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, false);