[Lldb-commits] [lldb] 6520ac7 - [lldb][test][AArch64] Add test for AArch32 compatibility execution (#211793)

via lldb-commits lldb-commits at lists.llvm.org
Tue Jul 28 02:03:12 PDT 2026


Author: David Spickett
Date: 2026-07-28T10:03:07+01:00
New Revision: 6520ac73f22d53e93e5ae8da6351a1714723497c

URL: https://github.com/llvm/llvm-project/commit/6520ac73f22d53e93e5ae8da6351a1714723497c
DIFF: https://github.com/llvm/llvm-project/commit/6520ac73f22d53e93e5ae8da6351a1714723497c.diff

LOG: [lldb][test][AArch64] Add test for AArch32 compatibility execution (#211793)

This should catch obvious problems like the one in #211692 in
future.

To run AArch32 programs on AArch64 you need a few things:
* Hardware that has AArch32 mode at EL0.
* A kernel built with CONFIG_COMPAT on.
* (usually) A bunch of 32-bit libraries installed.

We are dodging the 3rd one by compiling a standalone assembly
program that doesn't use any other libraries.

With clang being a cross-compiler and lld a cross-linker,
we should be able to do this without any 32-bit compatibility
libraries being installed.

To check for the first 2, we run that program normally.
If it fails to run, we don't have them. If it does run,
we have to double check that it's not being emulated. If
it is not being emulated, finally we can run it with lldb to
check that lldb works correctly.

Inside of lldb we place a breakpoint and read some
registers. GPRs are set to their own register number,
and FPR to their register number plus 1. So if we mixed
up the set order, we would know about it.

Tested on a Graviton 3 which allowed AArch32 execution and a Graviton 5
which did not. For the latter, the error without an emulator is:
```
$ ~/test.o
-bash: /home/davspi01/test.o: cannot execute binary file: Exec format error
```
This we detect when `Popen` fails. If you install qemu and setup
binfmt_misc, we detect that via. the exe path:
```
$ ~/test.o &
[2] 433187
$ ls -la /proc/433187/exe
lrwxrwxrwx 1 davspi01 davspi01 0 Jul 27 11:47 /proc/433187/exe -> /usr/bin/qemu-arm
```

Added: 
    lldb/test/API/linux/aarch64/aarch32_compat/Makefile
    lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
    lldb/test/API/linux/aarch64/aarch32_compat/main.s

Modified: 
    

Removed: 
    


################################################################################
diff  --git a/lldb/test/API/linux/aarch64/aarch32_compat/Makefile b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
new file mode 100644
index 0000000000000..469656e60078f
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
@@ -0,0 +1,21 @@
+ASM_SOURCES := main.s
+
+# This is to appease Makefile.rules, there is no main.c.
+C_SOURCES := main.c
+
+ASM_OBJS := $(ASM_SOURCES:.s=.o)
+
+# Do not automatically add the in-tree libcxx paths (we're not going to use
+# the system stdlib either).
+USE_SYSTEM_STDLIB := 1
+
+LD_EXTRAS := -target arm-unknown-linux-gnueabihf \
+	-fuse-ld=lld \
+	-nostdlib \
+	-static \
+	-Wl,-e,_start
+
+include Makefile.rules
+
+%.o: %.s
+	$(CC) $(CFLAGS) -target arm-unknown-linux-gnueabihf -c -x assembler $< -o $@

diff  --git a/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py b/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
new file mode 100644
index 0000000000000..b9a83b06cb59d
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
@@ -0,0 +1,117 @@
+"""
+Test launching AArch32 programs on AArch64.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+import subprocess
+
+
+class AArch64LinuxAArch32Compat(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIfRemote
+    @skipUnlessArch("aarch64")
+    @skipIfLLVMTargetMissing("ARM")
+    @skipUnlessPlatform(["linux"])
+    def test_aarch32_compat(self):
+        self.build()
+        test_program = self.getBuildArtifact("a.out")
+
+        try:
+            process = subprocess.Popen([test_program])
+        except (subprocess.SubprocessError, OSError):
+            self.skipTest("AArch32 programs are not supported.")
+
+        try:
+            # The program is running but might be emulated using binfmt_misc.
+            # If it is, the exe will be the emulator.
+            exe_path = os.readlink(f"/proc/{process.pid}/exe")
+            if not os.path.samefile(exe_path, test_program):
+                self.skipTest("AArch32 programs are being emulated.")
+        except OSError:
+            self.skipTest("Failed to detect AArch32 capability.")
+        finally:
+            process.kill()
+            process.wait()
+
+        self.runCmd("file " + test_program, CURRENT_EXECUTABLE_SET)
+        lldbutil.run_break_set_by_file_and_line(
+            self,
+            "main.s",
+            line_number("main.s", "// Loop forever."),
+            num_expected_locations=1,
+        )
+        self.runCmd("run", RUN_SUCCEEDED)
+
+        if self.process().GetState() == lldb.eStateExited:
+            self.fail("Test program failed to run.")
+
+        self.expect(
+            "thread list",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stopped", "stop reason = breakpoint"],
+        )
+
+        registers = (
+            self.dbg.GetSelectedTarget()
+            .GetProcess()
+            .GetSelectedThread()
+            .GetSelectedFrame()
+            .GetRegisters()
+        )
+
+        gpr = registers[0]
+        expected_gpr = {}
+
+        # r15 is ignored because it is the PC and we cannot predict its value.
+        for n in range(0, 15):
+            reg_name = f"r{n}"
+            if n == 13:
+                reg_name = "sp"
+            elif n == 14:
+                reg_name = "lr"
+
+            expected_gpr[reg_name] = n
+
+        # Top bits of CPSR are flags that could be anything, the bottom bits
+        # define the execution mode so we can be sure of their value. 0x10 means
+        # user mode and Arm state.
+        expected_gpr["cpsr"] = 0x10
+
+        for reg_name, expected_value in expected_gpr.items():
+            index = gpr.GetIndexOfChildWithName(reg_name)
+            self.assertNotEqual(index, lldb.LLDB_INVALID_INDEX32)
+            value = gpr.GetChildAtIndex(index).GetValueAsUnsigned()
+            if reg_name == "cpsr":
+                value &= 0xFF
+            self.assertEqual(expected_value, value)
+
+        fpr = registers[1]
+
+        # FIXME: there is a bug with fpr register indexes where it seems to be
+        # counting the GPRs as part of itself:
+        # (Pdb) fpr.GetChildAtIndex(0)
+        # (float) s0 = 1.40129846E-45
+        # (Pdb) fpr.GetIndexOfChildWithName("s0")
+        # 17
+        # (Pdb) fpr.GetChildAtIndex(17)
+        # (float) s17 = 2.52233724E-44
+        #
+        # See https://github.com/llvm/llvm-project/issues/211787.
+        #
+        # So we will assume that index 0 is s0 and not go via name lookup for
+        # fpr.
+
+        expected_fpr = {}
+        for n in range(32):
+            reg = fpr.GetChildAtIndex(n)
+            # We cannot call GetValueAsUnsigned on the value directly, as these
+            # are floating point registers.
+            error = lldb.SBError()
+            value = reg.GetData().GetUnsignedInt32(error, 0)
+            self.assertSuccess(error)
+            self.assertEqual(value, n + 1)

diff  --git a/lldb/test/API/linux/aarch64/aarch32_compat/main.s b/lldb/test/API/linux/aarch64/aarch32_compat/main.s
new file mode 100644
index 0000000000000..f15725282c421
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/main.s
@@ -0,0 +1,89 @@
+.globl _start
+.type _start, %function
+_start:
+  // Set FPU S registers to their register number plus 1.
+  mov r0, #1
+  vmov s0, r0
+  mov r0, #2
+  vmov s1, r0
+  mov r0, #3
+  vmov s2, r0
+  mov r0, #4
+  vmov s3, r0
+  mov r0, #5
+  vmov s4, r0
+  mov r0, #6
+  vmov s5, r0
+  mov r0, #7
+  vmov s6, r0
+  mov r0, #8
+  vmov s7, r0
+  mov r0, #9
+  vmov s8, r0
+  mov r0, #10
+  vmov s9, r0
+  mov r0, #11
+  vmov s10, r0
+  mov r0, #12
+  vmov s11, r0
+  mov r0, #13
+  vmov s12, r0
+  mov r0, #14
+  vmov s13, r0
+  mov r0, #15
+  vmov s14, r0
+  mov r0, #16
+  vmov s15, r0
+  mov r0, #17
+  vmov s16, r0
+  mov r0, #18
+  vmov s17, r0
+  mov r0, #19
+  vmov s18, r0
+  mov r0, #20
+  vmov s19, r0
+  mov r0, #21
+  vmov s20, r0
+  mov r0, #22
+  vmov s21, r0
+  mov r0, #23
+  vmov s22, r0
+  mov r0, #24
+  vmov s23, r0
+  mov r0, #25
+  vmov s24, r0
+  mov r0, #26
+  vmov s25, r0
+  mov r0, #27
+  vmov s26, r0
+  mov r0, #28
+  vmov s27, r0
+  mov r0, #29
+  vmov s28, r0
+  mov r0, #30
+  vmov s29, r0
+  mov r0, #31
+  vmov s30, r0
+  mov r0, #32
+  vmov s31, r0
+
+  // Set GPRs to their register number.
+  mov r0, #0
+  mov r1, #1
+  mov r2, #2
+  mov r3, #3
+  mov r4, #4
+  mov r5, #5
+  mov r6, #6
+  mov r7, #7
+  mov r8, #8
+  mov r9, #9
+  mov r10, #10
+  mov r11, #11
+  mov r12, #12
+  mov r13, #13
+  mov r14, #14
+  // r15 is the PC, leave this alone.
+end: // Loop forever.
+  b end
+


        


More information about the lldb-commits mailing list