Generate BTI information to SP manifest

For raw binary SPs the SP manifest needs to capture if for executable
regions GP bit needs to be enabled. Extend elf_segments_to_manifest.py
to generate the needed attributes based on the elf input file.

Signed-off-by: Gabor Toth <gabor.toth2@arm.com>
Change-Id: Ic0abfa977d67c15925c2dda8db8a81608f5c56c6
diff --git a/tools/python/elf_segments_to_manifest.py b/tools/python/elf_segments_to_manifest.py
index 5528164..378c318 100644
--- a/tools/python/elf_segments_to_manifest.py
+++ b/tools/python/elf_segments_to_manifest.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # SPDX-License-Identifier: BSD-3-Clause
 #
-# Copyright (c) 2021-2022, Arm Limited. All rights reserved.
+# Copyright (c) 2021-2024, Arm Limited. All rights reserved.
 
 """
 This module's goal is to take an ELF file as an input and extract the memory
@@ -12,9 +12,11 @@
 
 from enum import IntFlag
 from math import ceil
+from elftools import __version__ as module_version
 from elftools.elf.elffile import ELFFile
 from elftools.elf.constants import P_FLAGS
 
+assert module_version == "0.31"
 
 class ElfSegmentsToManifest:
     """
@@ -23,6 +25,33 @@
     """
     PAGE_SIZE = 4096
 
+    class GnuNotePropertySection:
+        """ Provides an API to process GNU note property section. """
+
+        GNU_PROPERTY_AARCH64_FEATURE_1_BTI = 1
+
+        def __init__(self, section):
+            self.section = section
+
+        def is_bti_enabled(self):
+            """ Returns whether any of the notes has a BTI enable property """
+
+            def is_bti_property(prop):
+                return prop['pr_type'] == "GNU_PROPERTY_AARCH64_FEATURE_1_AND" and \
+                    prop['pr_data'] & self.GNU_PROPERTY_AARCH64_FEATURE_1_BTI
+
+            def has_bti_property(note):
+                return note["n_name"] == 'GNU' and note["n_type"] == "NT_GNU_PROPERTY_TYPE_0" and \
+                    any(is_bti_property(p) for p in note["n_desc"])
+
+            return any(has_bti_property(n) for n in self.section.iter_notes())
+
+        @staticmethod
+        def is_matching_section(section):
+            """ Checks if the section is a GNU note property section """
+            return section.name == '.note.gnu.property'
+
+
     class Region:
         """ Describes a memory region and its attributes. """
 
@@ -31,6 +60,8 @@
             R = 0x01
             W = 0x02
             X = 0x04
+            S = 0x08
+            GP = 0x10
 
             def get_attr(self):
                 """ Queries the value of the attributes in manifest format. """
@@ -82,6 +113,11 @@
             self.end_address = region.end_address
             self.sections += region.sections
 
+        def set_bti_if_executable(self):
+            """ Sets GP flag if the region is executable. """
+            if self.attributes & self.ManifestMemAttr.X:
+                self.attributes |= self.ManifestMemAttr.GP
+
         def write_manifest(self, load_base_addr, manifest_file):
             """
             Writes the region into the manifest file. The address is adjusted by load_base_address.
@@ -131,7 +167,16 @@
                 return segment.header.p_align == ElfSegmentsToManifest.PAGE_SIZE
             assert is_aligned(segment), "Segments must be 4k aligned, check LD script"
             self.segment = segment
-            self.sections = [s for s in sections if self.segment.section_in_segment(s)]
+            self.sections = []
+            self.gnu_note = None
+
+            for section in sections:
+                if self.segment.section_in_segment(section):
+                    if ElfSegmentsToManifest.GnuNotePropertySection.is_matching_section(section):
+                        self.gnu_note = ElfSegmentsToManifest.GnuNotePropertySection(section)
+                    else:
+                        self.sections.append(section)
+
             self.regions = []
             self.merge_sections_to_regions()
 
@@ -150,6 +195,11 @@
                     self.regions.append(region)
                     current_region = region
 
+            # Set GP only for the executable regions if BTI is enabled in the segment
+            if self.gnu_note and self.gnu_note.is_bti_enabled():
+                for region in self.regions:
+                    region.set_bti_if_executable()
+
         def write_manifest(self, load_base_addr, manifest_file):
             """ Writes the regions into the manifest file. """
             for region in self.regions:
diff --git a/tools/python/requirements.txt b/tools/python/requirements.txt
index 81bf605..208c74f 100644
--- a/tools/python/requirements.txt
+++ b/tools/python/requirements.txt
@@ -1,7 +1,7 @@
 #
-# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
 
-pyelftools==0.28
+pyelftools==0.31