[Lldb-commits] [lldb] 4c66205 - [lldb][Linux] Add overlay and effective permissions to "memory region" (#184115)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Apr 24 05:33:34 PDT 2026
Author: David Spickett
Date: 2026-04-24T12:33:29Z
New Revision: 4c66205b070cdb8d18f9456ebbb5c592b5134487
URL: https://github.com/llvm/llvm-project/commit/4c66205b070cdb8d18f9456ebbb5c592b5134487
DIFF: https://github.com/llvm/llvm-project/commit/4c66205b070cdb8d18f9456ebbb5c592b5134487.diff
LOG: [lldb][Linux] Add overlay and effective permissions to "memory region" (#184115)
In this change I'm extending the "memory region" command to show users
the
overlay permissions that a protection key refers to, and the result of
applying that overlay to the page table permissions.
For example, protection key 0 refers to Perm0 in the por register.
```
(lldb) register read por
Perm0 = Read, Write, Execute
```
This is the default key, so many regions use it.
```
(lldb) memory region --all
<...>
[0x000ffffff7db0000-0x000ffffff7f40000) r-x /usr/lib/aarch64-linux-gnu/libc.so.6 PT_LOAD[0]
protection key: 0 (rwx, effective: r-x)
```
Protection keys can only change what was already enabled in the
page table. So we start with read and execute. Then a read/write/execute
overlay
is applied. We cannot add write, so the result is read and execute.
Here's an example of its use with a real crash (output edited):
```
(lldb) c
* thread #1, name = 'test.o', stop reason = signal SIGSEGV: failed protection key checks (fault address=0xffffff7d60000)
-> 106 read_only_page[0] = '?';
(lldb) memory region 0xffffff7d60000
[0x000ffffff7d60000-0x000ffffff7d70000) rw-
protection key: 6 (r--, effective: r--)
(lldb) register read por
Perm6 = Read
```
The calculation of permissions is implemented by a new ABI method.
It's in ABI for 2 reasons:
* These overlays are usually in a register (X86 and AArch64 are)
and that register name is architecture specific.
* The way the overlay values apply may differ between architecture.
AArch64 treats a set bit as adding a permission, but some may
treat it as removing.
Technically this is dependent on operating system and architecture.
However, so are the methods for removing non-address bits, and those
are in ABI too.
To test this I have changed the allocations in the test program
to use read+execute permissions by default. With read+write+execute
I could not observe that the overlay only changes enabled permissions.
Added:
Modified:
lldb/include/lldb/Target/ABI.h
lldb/source/Commands/CommandObjectMemory.cpp
lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
lldb/test/API/linux/aarch64/permission_overlay/main.c
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/ABI.h b/lldb/include/lldb/Target/ABI.h
index 1a1f1724222e3..3e2e7808c6c22 100644
--- a/lldb/include/lldb/Target/ABI.h
+++ b/lldb/include/lldb/Target/ABI.h
@@ -152,6 +152,33 @@ class ABI : public PluginInterface {
static lldb::ABISP FindPlugin(lldb::ProcessSP process_sp, const ArchSpec &arch);
+ struct MemoryPermissions {
+ // Both of these are sets of lldb::Permissions values.
+ // Overlay are the permissions being applied to the original permissions.
+ uint32_t overlay;
+ // Effective is the result of applying the overlay to the original
+ // permissions. Calculating this is done by the plugin because some
+ // permission overlays are done as positive (add permissions) and some as
+ // negative (remove permissions).
+ uint32_t effective;
+ };
+
+ /// Get the effective memory permissions that result when the permissions
+ /// referred to by a protection key are applied to the original permissions.
+ ///
+ /// This is intended for architectures that have some sort of permission
+ /// overlay system. Where the protection key is used to look up a set of
+ /// permissions that modifies the original permissions.
+ ///
+ /// \returns the overlay permissions (that the protection key refers to) and
+ /// the effective permissions. If the target does not have an overlay
+ /// system, or it does and the protection key is invalid, returns nullopt.
+ virtual std::optional<MemoryPermissions>
+ GetMemoryPermissions(lldb_private::RegisterContext ®_ctx,
+ unsigned protection_key, uint32_t original_permissions) {
+ return std::nullopt;
+ }
+
protected:
ABI(lldb::ProcessSP process_sp, std::unique_ptr<llvm::MCRegisterInfo> info_up)
: m_process_wp(process_sp), m_mc_register_info_up(std::move(info_up)) {
diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 00a254f60ae19..ac91b63e378a8 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1689,8 +1689,28 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
LazyBool is_shadow_stack = range_info.IsShadowStack();
if (is_shadow_stack == eLazyBoolYes)
result.AppendMessage("shadow stack: yes");
- if (std::optional<unsigned> protection_key = range_info.GetProtectionKey())
- result.AppendMessageWithFormatv("protection key: {0}", *protection_key);
+ if (std::optional<unsigned> protection_key =
+ range_info.GetProtectionKey()) {
+ Stream &strm = result.GetOutputStream();
+ strm << llvm::formatv("protection key: {0}", *protection_key);
+
+ if (const lldb::ABISP &abi = target.GetProcessSP()->GetABI()) {
+ if (auto permissions = abi->GetMemoryPermissions(
+ *m_exe_ctx.GetRegisterContext(), *protection_key,
+ range_info.GetLLDBPermissions())) {
+ strm << llvm::formatv(
+ " ({0}{1}{2}, effective: {3}{4}{5})",
+ permissions->overlay & lldb::ePermissionsReadable ? 'r' : '-',
+ permissions->overlay & lldb::ePermissionsWritable ? 'w' : '-',
+ permissions->overlay & lldb::ePermissionsExecutable ? 'x' : '-',
+ permissions->effective & lldb::ePermissionsReadable ? 'r' : '-',
+ permissions->effective & lldb::ePermissionsWritable ? 'w' : '-',
+ permissions->effective & lldb::ePermissionsExecutable ? 'x'
+ : '-');
+ }
+ }
+ strm.PutChar('\n');
+ }
const std::optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
index aa9c20b6bb2cf..83a777da3237e 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
@@ -884,3 +884,49 @@ void ABISysV_arm64::Initialize() {
void ABISysV_arm64::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}
+
+std::optional<ABISysV_arm64::MemoryPermissions>
+ABISysV_arm64::GetMemoryPermissions(lldb_private::RegisterContext ®_ctx,
+ unsigned protection_key,
+ uint32_t original_permissions) {
+ // The presence of the POR register means we have the Permission Overlay
+ // Extension.
+ // See Arm Architecture Reference manual "POR_EL0, Permission Overlay Register
+ // 0 (EL0)".
+ const RegisterInfo *por_info = reg_ctx.GetRegisterInfoByName("por");
+ if (!por_info)
+ return std::nullopt;
+
+ uint64_t por_value =
+ reg_ctx.ReadRegisterAsUnsigned(por_info, LLDB_INVALID_ADDRESS);
+ if (por_value == LLDB_INVALID_ADDRESS)
+ return std::nullopt;
+
+ // POR contains 16, 4-bit permission sets (though Linux limits this to 8
+ // useable sets).
+ if (protection_key >= 16)
+ return std::nullopt;
+
+ // Bit 3 - reserved, bit 2 - write, bit 1 - execute, bit 0 - read.
+ const uint64_t por_permissions = (por_value >> (protection_key * 4)) & 0xf;
+ uint32_t overlay = 0;
+ if (por_permissions & 4)
+ overlay |= lldb::ePermissionsWritable;
+ if (por_permissions & 2)
+ overlay |= lldb::ePermissionsExecutable;
+ if (por_permissions & 1)
+ overlay |= lldb::ePermissionsReadable;
+
+ uint32_t effective = original_permissions;
+
+ // Permission overlays cannot add permissions, they can only keep, or disable,
+ // what was originally set.
+ if (!(overlay & lldb::ePermissionsWritable))
+ effective &= ~lldb::ePermissionsWritable;
+ if (!(overlay & lldb::ePermissionsExecutable))
+ effective &= ~lldb::ePermissionsExecutable;
+ if (!(overlay & lldb::ePermissionsReadable))
+ effective &= ~lldb::ePermissionsReadable;
+
+ return MemoryPermissions{overlay, effective};
+}
diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
index 213fbf7417b2c..ae09e6dc87bd1 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
@@ -81,6 +81,14 @@ class ABISysV_arm64 : public ABIAArch64 {
lldb::addr_t FixCodeAddress(lldb::addr_t pc) override;
lldb::addr_t FixDataAddress(lldb::addr_t pc) override;
+ // If the Permission Overlay Extension is present, use the protection key
+ // to look up overlay permissions in por_el0 and apply them to the original
+ // permissions.
+ virtual std::optional<MemoryPermissions>
+ GetMemoryPermissions(lldb_private::RegisterContext ®_ctx,
+ unsigned protection_key,
+ uint32_t original_permissions) override;
+
protected:
lldb::ValueObjectSP
GetReturnValueObjectImpl(lldb_private::Thread &thread,
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index f4e67b2f402e0..affd66514b9bf 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -82,15 +82,25 @@ def test_poe_live(self):
# Unmapped region has no key (not even default).
self.expect("memory region 0", substrs=["protection key:"], matching=False)
- # The region has base permissions rwx, which is what we see here.
+ # The region has base permissions r-x, and overlay is r--. The result
+ # is that execution is disabled.
self.expect(
- "memory region read_only_page", substrs=["rwx", "protection key: 6"]
+ "memory region read_only_page",
+ substrs=["rw-", "protection key: 6 (r--, effective: r--)"],
+ )
+ # A region not assigned to a protection key has the default key 0. This
+ # key is rwx, but overlays cannot add permissions not already in the
+ # page table. So the execute permission is not enabled.
+ self.expect(
+ "memory region key_zero_page",
+ substrs=["rw-", "protection key: 0 (rwx, effective: rw-)"],
)
- # A region not assigned to a protection key has the default key 0.
- self.expect("memory region key_zero_page", substrs=["rwx", "protection key: 0"])
- # Protection keys should be on their own line.
- self.expect("memory region --all", patterns=["\nprotection key: [0-9]+\n"])
+ # Overlay permissions are on their own line.
+ self.expect(
+ "memory region --all",
+ patterns=["\nprotection key: [0-9]+ \([rwx-]{3}, effective: [rwx-]{3}\)\n"],
+ )
# Not passing this to the application allows us to fix the permissions
# using lldb, then continue to a normal exit.
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c b/lldb/test/API/linux/aarch64/permission_overlay/main.c
index ec2c0088b7084..5eb4782b9a6ca 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/main.c
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -76,9 +76,10 @@ int main(void) {
// Which leaves 7 keys available for programs to allocate.
const size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
- // pkeys can only subtract from the set of permissions in the page table,
- // so we set the page table to allow everything.
- const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
+ // pkeys can only subtract from the set of permissions in the page table.
+ // So we leave out execute here to check later that an overlay does not
+ // enable execution.
+ const int prot = PROT_READ | PROT_WRITE;
const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
// This page will have the default key 0.
More information about the lldb-commits
mailing list