[Lldb-commits] [lldb] [lldb][test][AArch64] Add test for AArch32 compatibility execution (PR #211793)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Jul 24 06:47:18 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: David Spickett (DavidSpickett)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/211793.diff
5 Files Affected:
- (modified) lldb/source/Host/linux/HostInfoLinux.cpp (+6)
- (added) lldb/test/API/linux/aarch64/aarch32_compat/Makefile (+17)
- (added) lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py (+117)
- (added) lldb/test/API/linux/aarch64/aarch32_compat/main.s (+89)
- (modified) lldb/unittests/Host/HostInfoTest.cpp (+8)
``````````diff
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/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
+
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/211793
More information about the lldb-commits
mailing list