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

Richard Earnshaw (lists) Richard.Earnshaw@arm.com
Wed Jul 9 14:02:13 GMT 2025


On 07/07/2025 17:49, Matthieu Longo wrote:
> From: Richard Ball <richard.ball@arm.com>
> 

This is still lacking a proper commit message.

> 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);



More information about the Binutils mailing list