[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