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

David Spickett via lldb-commits lldb-commits at lists.llvm.org
Tue Jul 28 01:30:57 PDT 2026


https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/211793

>From a6fa3f28f96658471b397eb17b1cac864b3a7369 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Wed, 22 Jul 2026 22:47:23 -0700
Subject: [PATCH 1/4] [lldb][AArch64] Fix launching Arm32 applications on
 AArch64 Linux

When LLDB runs on an AArch64 Linux, it adds both 64- and 32-bit
architectures as supported; see `PlatformLinux` ctor. The triple for the
32-bit variant is computed by replacing the architecture while keeping
all other fields; i.e., for the main host triple
`aarch64-unknown-linux-gnu`, the generated 32-bit triple is
`arm-unknown-linux-gnu`.

When a 32-bit target is created, its triple would be something like
`arm--linux-eabihf`. This triple is incompatible with the one added to
the supported architectures per condition in `ArchSpec::IsMatch()`,
which checks the environments to be compatible. As a result, LLDB fails
to launch this target:

> arm-linux-gnueabihf-g++ -g test.cpp -o test.out
> lldb
(lldb) file test.out
Current executable set to '/tmp/test.out' (arm).
(lldb) run
error: failed to launch or debug process

Clearing the environment of the computed triple for Arm32 allows the
compatibility check to pass.
---
 lldb/source/Host/linux/HostInfoLinux.cpp | 6 ++++++
 lldb/unittests/Host/HostInfoTest.cpp     | 8 ++++++++
 2 files changed, 14 insertions(+)

diff --git a/lldb/source/Host/linux/HostInfoLinux.cpp b/lldb/source/Host/linux/HostInfoLinux.cpp
index bb929f18505ed..caae470d79205 100644
--- a/lldb/source/Host/linux/HostInfoLinux.cpp
+++ b/lldb/source/Host/linux/HostInfoLinux.cpp
@@ -147,6 +147,12 @@ void HostInfoLinux::ComputeHostArchitectureSupport(ArchSpec &arch_32,
   if (arch_32.IsValid()) {
     if (arch_32.GetTriple().getVendor() == llvm::Triple::UnknownVendor)
       arch_32.GetTriple().setVendorName(llvm::StringRef());
+    // For Linux/AArch64, the environment components of triples for 64- and
+    // 32-bit targets do not match, for example, "aarch64--linux-gnu" and
+    // "arm--linux-eabihf". Clear the borrowed environment.
+    if (arch_64.IsValid() && arch_64.GetTriple().isAArch64() &&
+        arch_64.GetTriple().getEnvironment() == llvm::Triple::GNU)
+      arch_32.GetTriple().setEnvironment(llvm::Triple::UnknownEnvironment);
   }
   if (arch_64.IsValid()) {
     if (arch_64.GetTriple().getVendor() == llvm::Triple::UnknownVendor)
diff --git a/lldb/unittests/Host/HostInfoTest.cpp b/lldb/unittests/Host/HostInfoTest.cpp
index 0b3bae5c56f2f..997091ff2be3c 100644
--- a/lldb/unittests/Host/HostInfoTest.cpp
+++ b/lldb/unittests/Host/HostInfoTest.cpp
@@ -132,3 +132,11 @@ TEST_F(HostInfoTest, FindComponentInPath) {
   EXPECT_EQ("", HostInfoTester::FindComponentInPath("/path/to/foo", "bar"));
 }
 #endif
+
+#if defined(__linux__) && defined(__aarch64__)
+TEST_F(HostInfoTest, Arm32OnAArch64Compatibility) {
+  ArchSpec host_spec = HostInfo::GetArchitecture(HostInfo::eArchKind32);
+  ArchSpec target_spec("arm--linux-eabihf");
+  EXPECT_TRUE(target_spec.IsCompatibleMatch(host_spec));
+}
+#endif

>From 50258abe87bf744e0e28a9a2b906504a1f1298a8 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
Date: Fri, 24 Jul 2026 10:44:27 +0000
Subject: [PATCH 2/4] [lldb][test][AArch64] Add test for AArch32 compatibility
 execution

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 standlone 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 and
if not, we can run it inside lldb.

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'd know about it.
---
 .../API/linux/aarch64/aarch32_compat/Makefile |  17 +++
 .../TestAArch64LinuxAArch32Compat.py          | 117 ++++++++++++++++++
 .../API/linux/aarch64/aarch32_compat/main.s   |  89 +++++++++++++
 3 files changed, 223 insertions(+)
 create mode 100644 lldb/test/API/linux/aarch64/aarch32_compat/Makefile
 create mode 100644 lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
 create mode 100644 lldb/test/API/linux/aarch64/aarch32_compat/main.s

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..da3434e6b721b
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
@@ -0,0 +1,17 @@
+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)
+
+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 $@
\ No newline at end of file
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
+

>From 006ce42883023bfb5ce78a8f8cb19e49242536c4 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
Date: Fri, 24 Jul 2026 13:48:58 +0000
Subject: [PATCH 3/4] newline

---
 lldb/test/API/linux/aarch64/aarch32_compat/Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/test/API/linux/aarch64/aarch32_compat/Makefile b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
index da3434e6b721b..6ab3d2ff1dbbb 100644
--- a/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
@@ -14,4 +14,4 @@ LD_EXTRAS := -target arm-unknown-linux-gnueabihf \
 include Makefile.rules
 
 %.o: %.s
-	$(CC) $(CFLAGS) -target arm-unknown-linux-gnueabihf -c -x assembler $< -o $@
\ No newline at end of file
+	$(CC) $(CFLAGS) -target arm-unknown-linux-gnueabihf -c -x assembler $< -o $@

>From 6d825fd37c7740c4c00d98987a98b7e19f97e6ff Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
Date: Fri, 24 Jul 2026 14:48:29 +0000
Subject: [PATCH 4/4] try to avoid c++ includes

---
 lldb/test/API/linux/aarch64/aarch32_compat/Makefile | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lldb/test/API/linux/aarch64/aarch32_compat/Makefile b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
index 6ab3d2ff1dbbb..469656e60078f 100644
--- a/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/Makefile
@@ -5,6 +5,10 @@ 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 \



More information about the lldb-commits mailing list