[libc-commits] [libc] [libc] Set Root PAS in AArch64 baremetal page tables (PR #217857)
via libc-commits
libc-commits at lists.llvm.org
Fri Aug 21 02:21:53 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Mark Murray (MarkMurrayARM)
<details>
<summary>Changes</summary>
Set descriptor bit 11 for AArch64 baremetal normal-memory block mappings so FEAT_RME interprets them as Root PAS rather than Secure PAS. Keep NS clear and leave R-profile mappings unchanged.
Add a structural checker for the generated crt1 object that disassembles setup_mmu and verifies the normal-memory descriptors set NSE and do not set NS.
---
Full diff: https://github.com/llvm/llvm-project/pull/217857.diff
4 Files Affected:
- (modified) libc/startup/baremetal/aarch64/CMakeLists.txt (+26)
- (added) libc/startup/baremetal/aarch64/check-root-pas.py (+99)
- (modified) libc/startup/baremetal/aarch64/start.cpp (+20-1)
- (modified) libc/test/CMakeLists.txt (+3)
``````````diff
diff --git a/libc/startup/baremetal/aarch64/CMakeLists.txt b/libc/startup/baremetal/aarch64/CMakeLists.txt
index 09a34736de53a..1c15d1e61da5c 100644
--- a/libc/startup/baremetal/aarch64/CMakeLists.txt
+++ b/libc/startup/baremetal/aarch64/CMakeLists.txt
@@ -14,3 +14,29 @@ add_startup_object(
-fno-builtin
-Wno-global-constructors # To allow vector table initialization
)
+
+get_fq_target_name(crt1 fq_target_name)
+
+set(LIBC_AARCH64_OBJDUMP ${LLVM_TOOLS_BINARY_DIR}/llvm-objdump)
+if(CMAKE_HOST_WIN32)
+ set(LIBC_AARCH64_OBJDUMP ${LIBC_AARCH64_OBJDUMP}.exe)
+endif()
+
+if(LIBC_AARCH64_OBJDUMP)
+ set(root_pas_check_stamp ${CMAKE_CURRENT_BINARY_DIR}/crt1-root-pas.checked)
+ set(root_pas_check_script ${CMAKE_CURRENT_SOURCE_DIR}/check-root-pas.py)
+ add_custom_command(
+ OUTPUT ${root_pas_check_stamp}
+ COMMAND ${Python3_EXECUTABLE} ${root_pas_check_script}
+ --objdump ${LIBC_AARCH64_OBJDUMP}
+ $<TARGET_OBJECTS:${fq_target_name}>
+ COMMAND ${CMAKE_COMMAND} -E touch ${root_pas_check_stamp}
+ DEPENDS ${fq_target_name} $<TARGET_OBJECTS:${fq_target_name}>
+ ${LIBC_AARCH64_OBJDUMP} ${root_pas_check_script}
+ COMMENT "Checking AArch64 baremetal crt1 Root PAS page-table descriptors"
+ VERBATIM
+ )
+ add_custom_target(libc-startup-aarch64-root-pas-check
+ DEPENDS ${root_pas_check_stamp}
+ )
+endif()
diff --git a/libc/startup/baremetal/aarch64/check-root-pas.py b/libc/startup/baremetal/aarch64/check-root-pas.py
new file mode 100644
index 0000000000000..6cab47d9a81ef
--- /dev/null
+++ b/libc/startup/baremetal/aarch64/check-root-pas.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+import argparse
+import re
+import subprocess
+import sys
+
+MMU_BLOCK_NS = 1 << 5
+MMU_BLOCK_NSE = 1 << 11
+EXPECTED_NORMAL_ENTRY = 0xC05
+LEGACY_NORMAL_ENTRY = 0x405
+
+
+def error(message):
+ print(message, file=sys.stderr)
+ return 1
+
+
+def run(command):
+ return subprocess.run(
+ command,
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ ).stdout
+
+
+def extract_setup_mmu(disassembly):
+ lines = []
+ in_setup_mmu = False
+ symbol_re = re.compile(r"^[0-9a-fA-F]+ <.*>:$")
+
+ for line in disassembly.splitlines():
+ if symbol_re.match(line):
+ if "setup_mmu" in line:
+ in_setup_mmu = True
+ lines.append(line)
+ continue
+ if in_setup_mmu:
+ break
+
+ if in_setup_mmu:
+ lines.append(line)
+
+ if not lines:
+ raise ValueError("setup_mmu not found in disassembly")
+ return "\n".join(lines)
+
+
+def immediates(disassembly):
+ for match in re.finditer(r"#(0x[0-9a-fA-F]+|[0-9]+)", disassembly):
+ value = match.group(1)
+ yield int(value, 16 if value.startswith("0x") else 10)
+
+
+def check_disassembly(disassembly):
+ found_expected = False
+ found_legacy = False
+
+ for value in immediates(disassembly):
+ flags = value & 0xFFF
+ if flags == EXPECTED_NORMAL_ENTRY:
+ found_expected = True
+ if value & MMU_BLOCK_NS:
+ return error("normal-memory descriptor unexpectedly sets NS")
+ if not value & MMU_BLOCK_NSE:
+ return error("normal-memory descriptor does not set NSE")
+ elif flags == LEGACY_NORMAL_ENTRY:
+ found_legacy = True
+
+ if found_legacy:
+ return error("found normal-memory descriptor without NSE")
+ if not found_expected:
+ return error("normal-memory descriptor with NSE not found")
+ return 0
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--objdump", required=True)
+ parser.add_argument("object")
+ args = parser.parse_args(argv[1:])
+
+ try:
+ disassembly = run(
+ [args.objdump, "-d", "--no-show-raw-insn", args.object]
+ )
+ return check_disassembly(extract_setup_mmu(disassembly))
+ except (subprocess.CalledProcessError, ValueError) as err:
+ return error(str(err))
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/libc/startup/baremetal/aarch64/start.cpp b/libc/startup/baremetal/aarch64/start.cpp
index d65a81918a2cc..8835e1874e27b 100644
--- a/libc/startup/baremetal/aarch64/start.cpp
+++ b/libc/startup/baremetal/aarch64/start.cpp
@@ -73,7 +73,26 @@ uintptr_t get_stackheap_start() {
}
void setup_mmu() {
- constexpr uint64_t PAGE_TABLE_ENTRY = 0x405; // Index = 1, AF=1.
+ constexpr uint64_t PAGE_TABLE_BLOCK_DESCRIPTOR = 1ULL;
+ constexpr uint64_t PAGE_TABLE_BLOCK_ATTR_NORMAL = 1ULL << 2; // AttrIndx = 1.
+ constexpr uint64_t PAGE_TABLE_BLOCK_AF = 1ULL << 10;
+#if !defined(__ARM_ARCH_PROFILE) || __ARM_ARCH_PROFILE != 'R'
+ // With FEAT_RME, descriptor bit 11 is NSE rather than nG; NS=0,NSE=1
+ // selects Root PAS for Root execution. Without RME this retains the previous
+ // nG meaning, which is harmless for these baremetal identity mappings.
+ constexpr uint64_t PAGE_TABLE_BLOCK_ROOT_PAS = 1ULL << 11;
+#else
+ constexpr uint64_t PAGE_TABLE_BLOCK_ROOT_PAS = 0;
+#endif
+ constexpr uint64_t PAGE_TABLE_ENTRY =
+ PAGE_TABLE_BLOCK_DESCRIPTOR | PAGE_TABLE_BLOCK_ATTR_NORMAL |
+ PAGE_TABLE_BLOCK_AF | PAGE_TABLE_BLOCK_ROOT_PAS;
+ static_assert((PAGE_TABLE_ENTRY & (1ULL << 5)) == 0,
+ "normal-memory mappings must not set NS");
+#if !defined(__ARM_ARCH_PROFILE) || __ARM_ARCH_PROFILE != 'R'
+ static_assert((PAGE_TABLE_ENTRY & (1ULL << 11)) != 0,
+ "normal-memory mappings must set NSE for Root PAS");
+#endif
// Map the stack/heap as normal memory, but mark it non-executable for both
// privileged and unprivileged execution. This prevents accidentally executing
// code from writable stack/heap memory.
diff --git a/libc/test/CMakeLists.txt b/libc/test/CMakeLists.txt
index dd8db9a5166c0..9119909bffc5a 100644
--- a/libc/test/CMakeLists.txt
+++ b/libc/test/CMakeLists.txt
@@ -39,6 +39,9 @@ endif()
add_dependencies(check-libc-build libc-unit-tests-build)
add_dependencies(check-libc-build libc-hermetic-tests-build)
+if(TARGET libc-startup-aarch64-root-pas-check)
+ add_dependencies(check-libc-build libc-startup-aarch64-root-pas-check)
+endif()
add_subdirectory(UnitTest)
``````````
</details>
https://github.com/llvm/llvm-project/pull/217857
More information about the libc-commits
mailing list