[PATCH v8 07/19] readelf: dump Object Attributes v2
Matthieu Longo
matthieu.longo@arm.com
Tue Jul 15 11:39:10 GMT 2025
From: Richard Ball <richard.ball@arm.com>
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 | 319 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 319 insertions(+)
diff --git a/binutils/readelf.c b/binutils/readelf.c
index cfccdd2ba65..14fa445489d 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"
@@ -19435,6 +19436,320 @@ 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;
+}
+
+/* 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 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);
+ 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.
@@ -23462,6 +23777,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.1
More information about the Binutils
mailing list