https://github.com/DavidSpickett created
https://github.com/llvm/llvm-project/pull/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.
>From ce05713c28e35020c989edb9e41c0b9c6833c309 Mon Sep 17 00:00:00 2001
From: David Spickett <[email protected]>
Date: Tue, 20 Jan 2026 14:55:10 +0000
Subject: [PATCH 1/2] [lldb][Linux] Read memory protection keys for memory
regions
Memory protection keys (https://docs.kernel.org/core-api/protection-keys.html)
are implemented using two things:
* A key value attached to each page table entry.
* A set of permissions stored somewhere else (in a register on AArch64 and X86),
which is indexed into by that protection key.
So far I have updated LLDB to show the permissions part on AArch64 Linux
by reading the por register.
Now I am adding the ability to see which key each memory region is using.
The key is parsed from the /proc/.../smaps file, and so will only be present
for live processes, not core files.
This is sent as part of the qMemoryRegionInfo response as a new
"protection-key" key. As far as I know "memory protection keys" are Linux
specific, but I don't know of a good generic name for it, so I've
copied what smaps calls it.
I have updated the "memory region" command to show this key. A lot of the
time this will be the default 0 key. I considered hiding this, but decided
that for this initial support it's better to be explicit and verbose.
(this also prevents a Linux specific detail being added to a top level
command)
I have not yet added a way to see the protection key and effective permissions
in the same place, but I think users likely will want to do things like
that. So I have added 2 new methods to SBMemoryRegionInfo, HasProtectionKey
and GetProtectionKey. Users should not trust the result of GetProtectionKey
unless HasProtectionKey is true.
---
lldb/docs/resources/lldbgdbremote.md | 2 +
lldb/include/lldb/API/SBMemoryRegionInfo.h | 13 ++
lldb/include/lldb/Target/MemoryRegionInfo.h | 14 +-
lldb/source/API/SBMemoryRegionInfo.cpp | 12 ++
lldb/source/Commands/CommandObjectMemory.cpp | 4 +
.../Plugins/Process/Utility/LinuxProcMaps.cpp | 4 +
.../GDBRemoteCommunicationClient.cpp | 4 +
.../GDBRemoteCommunicationServerLLGS.cpp | 3 +
lldb/source/Target/MemoryRegionInfo.cpp | 16 +--
.../permission_overlay/TestAArch64LinuxPOE.py | 54 ++++++++
.../linux/aarch64/permission_overlay/main.c | 5 +
.../Process/Utility/LinuxProcMapsTest.cpp | 126 +++++++++++++-----
.../MemoryTagManagerAArch64MTETest.cpp | 2 +-
.../GDBRemoteCommunicationClientTest.cpp | 20 +++
.../Process/minidump/MinidumpParserTest.cpp | 114 ++++++++--------
15 files changed, 292 insertions(+), 101 deletions(-)
diff --git a/lldb/docs/resources/lldbgdbremote.md
b/lldb/docs/resources/lldbgdbremote.md
index 9aa7ad2259a6a..148b3a03aff86 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -1443,6 +1443,8 @@ tuples to return are:
listed (`dirty-pages:;`) indicates no dirty pages in
this memory region. The *absence* of this key means
that this stub cannot determine dirty pages.
+* `protection-key:<key>`- where `<key>` is an unsigned integer memory
protection
+ key.
If the address requested is not in a mapped region (e.g. we've jumped through
a NULL pointer and are at 0x0) currently lldb expects to get back the size
diff --git a/lldb/include/lldb/API/SBMemoryRegionInfo.h
b/lldb/include/lldb/API/SBMemoryRegionInfo.h
index dc5aa0858e1e3..9174675d2a11c 100644
--- a/lldb/include/lldb/API/SBMemoryRegionInfo.h
+++ b/lldb/include/lldb/API/SBMemoryRegionInfo.h
@@ -111,6 +111,19 @@ class LLDB_API SBMemoryRegionInfo {
/// or 0 if this information is unavailable.
int GetPageSize();
+ /// Returns whether this memory region has a memory protection key.
+ ///
+ /// \return
+ /// True if the region memory region has a memory protection key.
+ bool HasProtectionKey();
+
+ /// Returns the memory protection key of the memory region.
+ ///
+ /// \return
+ /// The memory protection key of the region. This value is only valid if
+ /// HasProtectionKey() is true.
+ uint32_t GetProtectionKey();
+
bool operator==(const lldb::SBMemoryRegionInfo &rhs) const;
bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const;
diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h
b/lldb/include/lldb/Target/MemoryRegionInfo.h
index dc37a7dbeda52..1513d93f1ab22 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -29,11 +29,13 @@ class MemoryRegionInfo {
OptionalBool execute, OptionalBool shared,
OptionalBool mapped, ConstString name, OptionalBool flash,
lldb::offset_t blocksize, OptionalBool memory_tagged,
- OptionalBool stack_memory, OptionalBool shadow_stack)
+ OptionalBool stack_memory, OptionalBool shadow_stack,
+ std::optional<unsigned> protection_key)
: m_range(range), m_read(read), m_write(write), m_execute(execute),
m_shared(shared), m_mapped(mapped), m_name(name), m_flash(flash),
m_blocksize(blocksize), m_memory_tagged(memory_tagged),
- m_is_stack_memory(stack_memory), m_is_shadow_stack(shadow_stack) {}
+ m_is_stack_memory(stack_memory), m_is_shadow_stack(shadow_stack),
+ m_protection_key(protection_key) {}
RangeType &GetRange() { return m_range; }
@@ -57,6 +59,8 @@ class MemoryRegionInfo {
OptionalBool IsShadowStack() const { return m_is_shadow_stack; }
+ std::optional<unsigned> GetProtectionKey() const { return m_protection_key; }
+
void SetReadable(OptionalBool val) { m_read = val; }
void SetWritable(OptionalBool val) { m_write = val; }
@@ -81,6 +85,8 @@ class MemoryRegionInfo {
void SetIsShadowStack(OptionalBool val) { m_is_shadow_stack = val; }
+ void SetProtectionKey(std::optional<unsigned> key) { m_protection_key = key;
}
+
// Get permissions as a uint32_t that is a mask of one or more bits from the
// lldb::Permissions
uint32_t GetLLDBPermissions() const {
@@ -111,7 +117,8 @@ class MemoryRegionInfo {
m_memory_tagged == rhs.m_memory_tagged &&
m_pagesize == rhs.m_pagesize &&
m_is_stack_memory == rhs.m_is_stack_memory &&
- m_is_shadow_stack == rhs.m_is_shadow_stack;
+ m_is_shadow_stack == rhs.m_is_shadow_stack &&
+ m_protection_key == rhs.m_protection_key;
}
bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs);
}
@@ -154,6 +161,7 @@ class MemoryRegionInfo {
OptionalBool m_memory_tagged = eDontKnow;
OptionalBool m_is_stack_memory = eDontKnow;
OptionalBool m_is_shadow_stack = eDontKnow;
+ std::optional<unsigned> m_protection_key = std::nullopt;
int m_pagesize = 0;
std::optional<std::vector<lldb::addr_t>> m_dirty_pages;
};
diff --git a/lldb/source/API/SBMemoryRegionInfo.cpp
b/lldb/source/API/SBMemoryRegionInfo.cpp
index cd25be5d52769..99bb6f0de4915 100644
--- a/lldb/source/API/SBMemoryRegionInfo.cpp
+++ b/lldb/source/API/SBMemoryRegionInfo.cpp
@@ -160,6 +160,18 @@ int SBMemoryRegionInfo::GetPageSize() {
return m_opaque_up->GetPageSize();
}
+bool SBMemoryRegionInfo::HasProtectionKey() {
+ LLDB_INSTRUMENT_VA(this);
+
+ return m_opaque_up->GetProtectionKey() != std::nullopt;
+}
+
+uint32_t SBMemoryRegionInfo::GetProtectionKey() {
+ LLDB_INSTRUMENT_VA(this);
+
+ return m_opaque_up->GetProtectionKey().value_or(0);
+}
+
bool SBMemoryRegionInfo::GetDescription(SBStream &description) {
LLDB_INSTRUMENT_VA(this, description);
diff --git a/lldb/source/Commands/CommandObjectMemory.cpp
b/lldb/source/Commands/CommandObjectMemory.cpp
index 93b6c1751b121..909761094f94b 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1694,6 +1694,10 @@ class CommandObjectMemoryRegion : public
CommandObjectParsed {
MemoryRegionInfo::OptionalBool is_shadow_stack =
range_info.IsShadowStack();
if (is_shadow_stack == MemoryRegionInfo::OptionalBool::eYes)
result.AppendMessage("shadow stack: yes");
+ if (std::optional<unsigned> protection_key =
+ range_info.GetProtectionKey())
+ result.AppendMessageWithFormat("protection key: %" PRIu32 "\n",
+ *protection_key);
const std::optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
diff --git a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
index 2ed896327a2f8..eea0bf245877d 100644
--- a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
+++ b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
@@ -174,6 +174,10 @@ void lldb_private::ParseLinuxSMapRegions(llvm::StringRef
linux_smap,
region->SetMemoryTagged(MemoryRegionInfo::eYes);
else if (flag == "ss")
region->SetIsShadowStack(MemoryRegionInfo::eYes);
+ } else if (name == "ProtectionKey") {
+ unsigned key = 0;
+ if (!value.ltrim().getAsInteger(10, key))
+ region->SetProtectionKey(key);
}
} else {
// Orphaned settings line
diff --git
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 738e4013b6154..c639090ebe9af 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -1677,6 +1677,10 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
dirty_page_list.push_back(page);
}
region_info.SetDirtyPageList(dirty_page_list);
+ } else if (name == "protection-key") {
+ unsigned protection_key = 0;
+ if (!value.getAsInteger(10, protection_key))
+ region_info.SetProtectionKey(protection_key);
}
}
diff --git
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 2f62415446b7a..72b6d7adbbf96 100644
---
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -2907,6 +2907,9 @@
GDBRemoteCommunicationServerLLGS::Handle_qMemoryRegionInfo(
response.PutStringAsRawHex8(name.GetStringRef());
response.PutChar(';');
}
+
+ if (std::optional<unsigned> protection_key =
region_info.GetProtectionKey())
+ response.Printf("protection-key:%" PRIu32 ";", *protection_key);
}
return SendPacketNoLock(response.GetString());
diff --git a/lldb/source/Target/MemoryRegionInfo.cpp
b/lldb/source/Target/MemoryRegionInfo.cpp
index 979e45ad023af..7bdb3dc4f3168 100644
--- a/lldb/source/Target/MemoryRegionInfo.cpp
+++ b/lldb/source/Target/MemoryRegionInfo.cpp
@@ -12,14 +12,14 @@ using namespace lldb_private;
llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS,
const MemoryRegionInfo &Info) {
- return OS << llvm::formatv("MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, "
- "{5}, `{6}`, {7}, {8}, {9}, {10}, {11})",
- Info.GetRange().GetRangeBase(),
- Info.GetRange().GetRangeEnd(), Info.GetReadable(),
- Info.GetWritable(), Info.GetExecutable(),
- Info.GetMapped(), Info.GetName(), Info.GetFlash(),
- Info.GetBlocksize(), Info.GetMemoryTagged(),
- Info.IsStackMemory(), Info.IsShadowStack());
+ return OS << llvm::formatv(
+ "MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, "
+ "{5}, `{6}`, {7}, {8}, {9}, {10}, {11}, {12})",
+ Info.GetRange().GetRangeBase(), Info.GetRange().GetRangeEnd(),
+ Info.GetReadable(), Info.GetWritable(), Info.GetExecutable(),
+ Info.GetMapped(), Info.GetName(), Info.GetFlash(),
+ Info.GetBlocksize(), Info.GetMemoryTagged(), Info.IsStackMemory(),
+ Info.IsShadowStack(), Info.GetProtectionKey());
}
void llvm::format_provider<MemoryRegionInfo::OptionalBool>::format(
diff --git
a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index 056267a2dc900..3b4bd8e55cf47 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -79,6 +79,50 @@ def test_poe_live(self):
self.expect("expression expr_function()", substrs=["$0 = 1"])
self.expect("register read por", substrs=[self.EXPECTED_POR])
+ # 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.
+ self.expect(
+ "memory region read_only_page", substrs=["rwx", "protection key:
6"]
+ )
+ # 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 are also in SBMemoryRegionInfo.
+ process = self.dbg.GetSelectedTarget().GetProcess()
+ info = lldb.SBMemoryRegionInfo()
+
+ frame = (
+ self.dbg.GetSelectedTarget()
+ .GetProcess()
+ .GetSelectedThread()
+ .GetSelectedFrame()
+ )
+
+ err = lldb.SBError()
+ read_only_addr = frame.GetValueForVariablePath(
+ "read_only_page"
+ ).GetValueAsUnsigned(err)
+ self.assertTrue(err.Success())
+ key_zero_addr = frame.GetValueForVariablePath(
+ "key_zero_page"
+ ).GetValueAsUnsigned(err)
+ self.assertTrue(err.Success())
+
+ region_api_info = [
+ # An unmapped region will have no key at all.
+ # The getter returns 0 as a default, but should not be trusted.
+ (0, False, 0),
+ (read_only_addr, True, 6),
+ (key_zero_addr, True, 0),
+ ]
+ for addr, valid, key in region_api_info:
+ err = process.GetMemoryRegionInfo(addr, info)
+ self.assertTrue(err.Success())
+ self.assertEqual(info.HasProtectionKey(), valid)
+ self.assertEqual(info.GetProtectionKey(), key)
+
# Not passing this to the application allows us to fix the permissions
# using lldb, then continue to a normal exit.
self.runCmd("process handle SIGSEGV --pass false")
@@ -127,3 +171,13 @@ def test_poe_core(self):
"register read por",
substrs=[f" {self.EXPECTED_POR}\n" +
self.EXPECTED_POR_FIELDS],
)
+
+ # Protection keys are listed in /proc/<pid>/smaps, which is not
included
+ # in core files.
+ self.expect("memory region --all", substrs=["protection key:"],
matching=False)
+
+ # No region should have a key at all, not even a default.
+ process = self.dbg.GetSelectedTarget().GetProcess()
+ for region in
self.dbg.GetSelectedTarget().GetProcess().GetMemoryRegions():
+ self.assertEqual(region.HasProtectionKey(), False)
+ self.assertEqual(region.GetProtectionKey(), 0)
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c
b/lldb/test/API/linux/aarch64/permission_overlay/main.c
index 6f47ba9d774da..ec2c0088b7084 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/main.c
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -81,6 +81,11 @@ int main(void) {
const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+ // This page will have the default key 0.
+ char *key_zero_page = mmap(NULL, page_size, prot, flags, -1, 0);
+ if (key_zero_page == MAP_FAILED)
+ exit(2);
+
// Later we will use this to cause a protection key fault.
char *read_only_page = NULL;
diff --git a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
index d94bb4f4db982..f3f40cbc2f19d 100644
--- a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
+++ b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
@@ -93,20 +93,21 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[abc]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
"unexpected /proc/{pid}/maps exec permission char"),
// Single entry
std::make_tuple(
"55a4512f7000-55a451b68000 rw-p 00000000 00:00 0 [heap]",
MemoryRegionInfos{
- MemoryRegionInfo(
- make_range(0x55a4512f7000, 0x55a451b68000),
- MemoryRegionInfo::eYes, MemoryRegionInfo::eYes,
- MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
- MemoryRegionInfo::eYes, ConstString("[heap]"),
- MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo(make_range(0x55a4512f7000, 0x55a451b68000),
+ MemoryRegionInfo::eYes,
MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eYes, ConstString("[heap]"),
+ MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
""),
// Multiple entries
@@ -116,27 +117,30 @@ INSTANTIATE_TEST_SUITE_P(
"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 "
"[vsyscall]",
MemoryRegionInfos{
- MemoryRegionInfo(
- make_range(0x7fc090021000, 0x7fc094000000),
- MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
- MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
- MemoryRegionInfo::eYes, ConstString(nullptr),
- MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
- MemoryRegionInfo(
- make_range(0x7fc094000000, 0x7fc094a00000),
- MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
- MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
- MemoryRegionInfo::eYes, ConstString(nullptr),
- MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo(make_range(0x7fc090021000, 0x7fc094000000),
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eYes, ConstString(nullptr),
+ MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, std::nullopt),
+ MemoryRegionInfo(make_range(0x7fc094000000, 0x7fc094a00000),
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eYes, ConstString(nullptr),
+ MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, std::nullopt),
MemoryRegionInfo(
make_range(0xffffffffff600000, 0xffffffffff601000),
MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
MemoryRegionInfo::eYes, ConstString("[vsyscall]"),
MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+ std::nullopt),
},
"")));
@@ -163,7 +167,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
"malformed /proc/{pid}/smaps entry, missing dash between address "
"range"),
@@ -184,7 +188,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
""),
// Single shared region parses, has no flags
@@ -197,7 +201,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eYes, MemoryRegionInfo::eYes,
ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
""),
// Single region with flags, other lines ignored
@@ -213,7 +217,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eNo),
+ MemoryRegionInfo::eNo, std::nullopt),
},
""),
// Whitespace ignored
@@ -227,7 +231,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eNo),
+ MemoryRegionInfo::eNo, std::nullopt),
},
""),
// VmFlags line means it has flag info, but nothing is set
@@ -241,7 +245,7 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eNo, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eNo),
+ MemoryRegionInfo::eNo, std::nullopt),
},
""),
// Handle some pages not having a flags line
@@ -258,14 +262,14 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
MemoryRegionInfo(
make_range(0x3333, 0x4444), MemoryRegionInfo::eYes,
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString("[bar]"), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eNo),
+ MemoryRegionInfo::eNo, std::nullopt),
},
""),
// Handle no pages having a flags line (older kernels)
@@ -283,14 +287,14 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
MemoryRegionInfo(
make_range(0x3333, 0x4444), MemoryRegionInfo::eYes,
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eDontKnow),
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
""),
// We must look for exact flag strings, ignoring substrings of longer
@@ -305,7 +309,61 @@ INSTANTIATE_TEST_SUITE_P(
MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
MemoryRegionInfo::eNo, MemoryRegionInfo::eDontKnow,
- MemoryRegionInfo::eNo),
+ MemoryRegionInfo::eNo, std::nullopt),
+ },
+ ""),
+ // 0 is the default protection key.
+ std::make_tuple(
+ "0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: 0",
+ MemoryRegionInfos{
+ MemoryRegionInfo(
+ make_range(0, 0), MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+ ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, 0),
+ },
+ ""),
+ std::make_tuple(
+ "0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: 99",
+ MemoryRegionInfos{
+ MemoryRegionInfo(
+ make_range(0, 0), MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+ ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, 99),
+ },
+ ""),
+ std::make_tuple(
+ "0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: not_an_integer",
+ MemoryRegionInfos{
+ MemoryRegionInfo(
+ make_range(0, 0), MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+ ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, std::nullopt),
+ },
+ ""),
+ // Should be unsigned.
+ std::make_tuple(
+ "0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: -24",
+ MemoryRegionInfos{
+ MemoryRegionInfo(
+ make_range(0, 0), MemoryRegionInfo::eYes,
+ MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+ MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+ ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+ MemoryRegionInfo::eDontKnow, std::nullopt),
},
"")));
diff --git a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
index 30199bfe5c254..4afae00b8fa5f 100644
--- a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
+++ b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
@@ -236,7 +236,7 @@ static MemoryRegionInfo MakeRegionInfo(lldb::addr_t base,
lldb::addr_t size,
MemoryRegionInfo::eYes, ConstString(), MemoryRegionInfo::eNo, 0,
/*memory_tagged=*/
tagged ? MemoryRegionInfo::eYes : MemoryRegionInfo::eNo,
- MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow);
+ MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow, std::nullopt);
}
TEST(MemoryTagManagerAArch64MTETest, MakeTaggedRange) {
diff --git
a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
index a5156326a1447..8dacbf136e931 100644
--- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
+++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
@@ -401,6 +401,7 @@ TEST_F(GDBRemoteCommunicationClientTest,
GetMemoryRegionInfo) {
region_info.IsStackMemory());
EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow,
region_info.IsShadowStack());
+ EXPECT_EQ(std::nullopt, region_info.GetProtectionKey());
result = std::async(std::launch::async, [&] {
return client.GetMemoryRegionInfo(addr, region_info);
@@ -433,6 +434,25 @@ TEST_F(GDBRemoteCommunicationClientTest,
GetMemoryRegionInfo) {
"start:a000;size:2000;type:heap;");
EXPECT_TRUE(result.get().Success());
EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsStackMemory());
+
+ result = std::async(std::launch::async, [&] {
+ return client.GetMemoryRegionInfo(addr, region_info);
+ });
+
+ HandlePacket(server, "qMemoryRegionInfo:a000",
+ "start:a000;size:2000;protection-key:42;");
+ EXPECT_TRUE(result.get().Success());
+ ASSERT_THAT(region_info.GetProtectionKey(),
+ ::testing::Optional(::testing::Eq(42)));
+
+ result = std::async(std::launch::async, [&] {
+ return client.GetMemoryRegionInfo(addr, region_info);
+ });
+
+ HandlePacket(server, "qMemoryRegionInfo:a000",
+ "start:a000;size:2000;protection-key:not_a_number;");
+ EXPECT_TRUE(result.get().Success());
+ ASSERT_THAT(region_info.GetProtectionKey(), std::nullopt);
}
TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfoInvalidResponse) {
diff --git a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
index 44f653c6fa135..0bc31ec8eeee2 100644
--- a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
+++ b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
@@ -382,23 +382,24 @@ TEST_F(MinidumpParserTest, GetMemoryRegionInfo) {
EXPECT_THAT(
parser->BuildMemoryRegions(),
- testing::Pair(testing::ElementsAre(
- MemoryRegionInfo({0x0, 0x10000}, no, no, no, unknown,
- no, ConstString(), unknown, 0,
unknown,
- unknown, unknown),
- MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x40000, 0x1000}, yes, no, no,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown)),
- true));
+ testing::Pair(
+ testing::ElementsAre(
+ MemoryRegionInfo({0x0, 0x10000}, no, no, no, unknown, no,
+ ConstString(), unknown, 0, unknown, unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, unknown, yes,
+ ConstString(), unknown, 0, unknown, unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, unknown, yes,
+ ConstString(), unknown, 0, unknown, unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, unknown, yes,
+ ConstString(), unknown, 0, unknown, unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, unknown, yes,
+ ConstString(), unknown, 0, unknown, unknown,
+ unknown, std::nullopt)),
+ true));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemoryList) {
@@ -420,14 +421,15 @@ TEST_F(MinidumpParserTest,
GetMemoryRegionInfoFromMemoryList) {
EXPECT_THAT(
parser->BuildMemoryRegions(),
- testing::Pair(testing::ElementsAre(
- MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown)),
- false));
+ testing::Pair(
+ testing::ElementsAre(
+ MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, unknown,
+ yes, ConstString(), unknown, 0, unknown,
unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, unknown,
+ yes, ConstString(), unknown, 0, unknown,
unknown,
+ unknown, std::nullopt)),
+ false));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemory64List) {
@@ -437,14 +439,15 @@ TEST_F(MinidumpParserTest,
GetMemoryRegionInfoFromMemory64List) {
// we don't have a MemoryInfoListStream.
EXPECT_THAT(
parser->BuildMemoryRegions(),
- testing::Pair(testing::ElementsAre(
- MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown,
- unknown, yes, ConstString(), unknown,
- 0, unknown, unknown, unknown)),
- false));
+ testing::Pair(
+ testing::ElementsAre(
+ MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, unknown,
+ yes, ConstString(), unknown, 0, unknown,
unknown,
+ unknown, std::nullopt),
+ MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, unknown,
+ yes, ConstString(), unknown, 0, unknown,
unknown,
+ unknown, std::nullopt)),
+ false));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) {
@@ -468,27 +471,28 @@ TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) {
ConstString app_process("/system/bin/app_process");
ConstString linker("/system/bin/linker");
ConstString liblog("/system/lib/liblog.so");
- EXPECT_THAT(
- parser->BuildMemoryRegions(),
- testing::Pair(
- testing::ElementsAre(
- MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, no, yes,
- app_process, unknown, 0, unknown, unknown,
- unknown),
- MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, no, yes,
- app_process, unknown, 0, unknown, unknown,
- unknown),
- MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, no, yes,
- ConstString(), unknown, 0, unknown, unknown,
- unknown),
- MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, no, yes,
- ConstString(), unknown, 0, unknown, unknown,
- unknown),
- MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, no, yes,
- linker, unknown, 0, unknown, unknown, unknown),
- MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, no, yes,
- liblog, unknown, 0, unknown, unknown, unknown)),
- true));
+ EXPECT_THAT(parser->BuildMemoryRegions(),
+ testing::Pair(
+ testing::ElementsAre(
+ MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, no,
+ yes, app_process, unknown, 0, unknown,
+ unknown, unknown, std::nullopt),
+ MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, no,
+ yes, app_process, unknown, 0, unknown,
+ unknown, unknown, std::nullopt),
+ MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, no,
+ yes, ConstString(), unknown, 0, unknown,
+ unknown, unknown, std::nullopt),
+ MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, no,
+ yes, ConstString(), unknown, 0, unknown,
+ unknown, unknown, std::nullopt),
+ MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, no,
+ yes, linker, unknown, 0, unknown,
+ unknown, unknown, std::nullopt),
+ MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, no,
+ yes, liblog, unknown, 0, unknown,
+ unknown, unknown, std::nullopt)),
+ true));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMapsError) {
@@ -508,7 +512,7 @@ TEST_F(MinidumpParserTest,
GetMemoryRegionInfoLinuxMapsError) {
testing::Pair(testing::ElementsAre(MemoryRegionInfo(
{0x400fc000, 0x1000}, yes, yes, yes, no, yes,
ConstString(nullptr), unknown, 0, unknown,
- unknown, unknown)),
+ unknown, unknown, std::nullopt)),
true));
}
>From 68521efced5d4138085a3d72bd22cdb5854ebcc8 Mon Sep 17 00:00:00 2001
From: David Spickett <[email protected]>
Date: Thu, 22 Jan 2026 14:53:26 +0000
Subject: [PATCH 2/2] [lldb][Linux] Add overlay and effective permissions to
"memory region"
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.
---
lldb/include/lldb/Target/ABI.h | 25 ++++++++++
lldb/source/Commands/CommandObjectMemory.cpp | 23 +++++++++-
.../Plugins/ABI/AArch64/ABISysV_arm64.cpp | 46 +++++++++++++++++++
.../Plugins/ABI/AArch64/ABISysV_arm64.h | 5 ++
.../permission_overlay/TestAArch64LinuxPOE.py | 21 +++++++--
.../linux/aarch64/permission_overlay/main.c | 7 +--
6 files changed, 118 insertions(+), 9 deletions(-)
diff --git a/lldb/include/lldb/Target/ABI.h b/lldb/include/lldb/Target/ABI.h
index 1a1f1724222e3..4eb38d33c9eed 100644
--- a/lldb/include/lldb/Target/ABI.h
+++ b/lldb/include/lldb/Target/ABI.h
@@ -152,6 +152,31 @@ 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 permissions being overlayed for a given memory key, and the
+ /// resulting permissions after applying the overlay. Typically the
protection
+ /// key is used to look up in some architecture specific set of permissions.
+ /// On AArch64, this is the POR register, used by the Permission Overlay
+ /// Extension.
+ ///
+ /// Returns std::nullopt if the current target does not have such an overlay
+ /// system, or if the protection key is not valid.
+ 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 909761094f94b..f1e3479fc8f69 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1695,10 +1695,29 @@ class CommandObjectMemoryRegion : public
CommandObjectParsed {
if (is_shadow_stack == MemoryRegionInfo::OptionalBool::eYes)
result.AppendMessage("shadow stack: yes");
if (std::optional<unsigned> protection_key =
- range_info.GetProtectionKey())
- result.AppendMessageWithFormat("protection key: %" PRIu32 "\n",
+ range_info.GetProtectionKey()) {
+ result.AppendMessageWithFormat("protection key: %" PRIu32,
*protection_key);
+ if (const lldb::ABISP &abi = target.GetProcessSP()->GetABI()) {
+ uint32_t base_permissions = range_info.GetLLDBPermissions();
+ if (auto permissions =
+ abi->GetMemoryPermissions(*m_exe_ctx.GetRegisterContext(),
+ *protection_key, base_permissions)) {
+ result.AppendMessageWithFormatv(
+ " ({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'
+ : '-');
+ }
+ } else
+ result.AppendMessage("");
+ }
+
const std::optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
if (dirty_page_list) {
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..53c79ee2d6eb1 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
@@ -81,6 +81,11 @@ class ABISysV_arm64 : public ABIAArch64 {
lldb::addr_t FixCodeAddress(lldb::addr_t pc) override;
lldb::addr_t FixDataAddress(lldb::addr_t pc) override;
+ 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 3b4bd8e55cf47..9b20b39095789 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -82,12 +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-)"],
+ )
+
+ # Overlay permissions are on their own line.
+ self.expect(
+ "memory region --all",
+ patterns=["\nprotection key: [0-9]+ \([rwx-]{3}, effective:
[rwx-]{3}\)\n"],
)
- # 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 are also in SBMemoryRegionInfo.
process = self.dbg.GetSelectedTarget().GetProcess()
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.
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits