David Spickett 7dbfc2bd00
[lldb][AArch64][Linux] Add support for the Permission Overlay Extension (POE) (#177145)
This change adds initial support for managing the Permission Overlay
Extension (POE). This extension allows userspace programs to change
memory permissions without making a sycall.

This is used to implement Linux's memory protection keys
(https://docs.kernel.org/core-api/protection-keys.html) on AArch64.

Overview of POE:
* Page table entries have a set of permissions. To change these, a
program would have to use a syscall which adds overhead.
* 3 bits of the page table entry are used for a protection key 0-7.
* POE adds a new register "por" (POR_EL0 in the manual) which stores
4-bit sets of permissions.
* The protection key is an index into this por register.
* Permissions in POR are applied on top of the page table permissions,
but may only remove permissions. For example, if you overlay
read/execute over read/write, the result is read. Since execute was not
in the page table permissions.
* This register can be modified without leaving userspace, making
permission changes faster.

To help debug this, I have made the following changes to LLDB:
* Ability to read and write the por register.
* Save and restore of por when running expressions.
* Register field definitions to print the por permissions in a human
readable form.
* Recognition of memory protection key faults as a distinct type of
SIGSEGV (this will apply to Linux on any architecture).

There are a few more features to add around memory region information,
that will be in follow up changes. As will documentation and release
notes for all the POE features.
2026-02-19 10:25:31 +00:00

81 lines
2.5 KiB
Python

"""
Platform-agnostic helper to query for CPU features.
"""
import re
class CPUFeature:
def __init__(self, linux_cpu_info_flag: str = None, darwin_sysctl_key: str = None):
self.cpu_info_flag = linux_cpu_info_flag
self.sysctl_key = darwin_sysctl_key
def __str__(self):
for arch_class in ALL_ARCHS:
for feat_var in dir(arch_class):
if self == getattr(arch_class, feat_var):
return f"{arch_class.__name__}.{feat_var}"
raise AssertionError("unreachable")
def is_supported(self, triple, cmd_runner):
if re.match(".*-.*-linux", triple):
err_msg, res = self._is_supported_linux(cmd_runner)
elif re.match(".*-apple-.*", triple):
err_msg, res = self._is_supported_darwin(cmd_runner)
else:
err_msg, res = None, False
if err_msg:
print(f"CPU feature check failed: {err_msg}")
return res
def _is_supported_linux(self, cmd_runner):
if not self.cpu_info_flag:
return f"Unspecified cpuinfo flag for {self}", False
cmd = "cat /proc/cpuinfo"
err, retcode, output = cmd_runner(cmd)
if err.Fail() or retcode != 0:
return output, False
# Assume that every processor presents the same features.
# Look for the first "Features: ...." line. Features are space separated.
if m := re.search(r"Features\s*: (.*)\n", output):
features = m.group(1).split()
return None, (self.cpu_info_flag in features)
return 'No "Features:" line found in /proc/cpuinfo', False
def _is_supported_darwin(self, cmd_runner):
if not self.sysctl_key:
return f"Unspecified sysctl key for {self}", False
cmd = f"sysctl -n {self.sysctl_key}"
err, retcode, output = cmd_runner(cmd)
if err.Fail() or retcode != 0:
return output, False
return None, (output.strip() == "1")
class AArch64:
FPMR = CPUFeature("fpmr")
POE = CPUFeature("poe")
GCS = CPUFeature("gcs")
MTE = CPUFeature("mte", "hw.optional.arm.FEAT_MTE4")
MTE_STORE_ONLY = CPUFeature("mtestoreonly")
PTR_AUTH = CPUFeature("paca", "hw.optional.arm.FEAT_PAuth2")
SME = CPUFeature("sme", "hw.optional.arm.FEAT_SME")
SME_FA64 = CPUFeature("smefa64")
SME2 = CPUFeature("sme2", "hw.optional.arm.FEAT_SME2")
SVE = CPUFeature("sve")
class Loong:
LASX = CPUFeature("lasx")
LSX = CPUFeature("lsx")
ALL_ARCHS = [AArch64, Loong]