From 79a752f2d424882ffcd2af122b4d0a90d93cbdbb Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Thu, 9 Oct 2025 19:10:07 -0700 Subject: [PATCH 1/3] [lldb] Cortex-M exception unwind API test cleanup This test, with a corefile created via yaml2macho-core plus an ObjectFileJSON binary with symbol addresses and ranges, was failing on some machines/CI because the wrong ABI was being picked. The bytes of the functions were not included in the yaml or .json binary. The unwind falls back to using the ABI plugin default unwind plans. We have two armv7 ABIs - the Darwin ABI that always uses r7 as the frame pointer, and the AAPCS ABI which uses r11 code. In reality, armv7 code uses r11 in arm mode, r7 in thumb code. But the ABI ArchDefaultUnwindPlan doesn't have any access to the Target's ArchSpec or Process register state, to determine the correct processor state (arm or thumb). And in fact, on Cortex-M targets, the instructions are always thumb, so the arch default unwind plan (hardcoded r11) is always wrong. The corefile doesn't specify a vendor/os, only a cpu. The object file json specifies the armv7m-apple-* triple, which will select the correct ABI plugin, and the test runs. In some cases, it looks like the Process ABI was fetched after opening the corefile, but before the binary.json was loaded and corrected the Target's ArchSpec. And we never re-evaluate the ABI once it is set, in a Process. When we picked the AAPCS armv7 ABI, we would try to use r11 as frame pointer, and the unwind would stop after one stack frame. I'm stepping around this problem by (1) adding the register bytes of the prologues of every test function in the backtrace, and (2) shortening the function ranges (in binary.json) to specify that the functions are all just long enough for the prologue where execution is stopped. The instruction emulation plugin will fail if it can't get all of the bytes from the function instructions, so I hacked the function sizes in the .json to cover the prologue plus one and changed the addresses in the backtrace to fit within those ranges. [ updated this commit to keep the @skipIfRemote on the API test because two remote CI bots are failing for reasons I don't quite see. ] (cherry picked from commit 47d3c5f3cb159fd261ab5c3efa55afae246c5e0d) --- .../TestCortexMExceptionUnwind.py | 8 +++-- .../armv7m-nofpu-exception.yaml | 29 +++++++++++++++++-- .../unwind/cortex-m-exception/binary.json | 6 ++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py index a4dc69617c1d3..b40af33ae0179 100644 --- a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py @@ -11,6 +11,7 @@ class TestCortexMExceptionUnwind(TestBase): NO_DEBUG_INFO_TESTCASE = True + @skipIfRemote def test_no_fpu(self): """Test that we can backtrace correctly through an ARM Cortex-M Exception return stack""" @@ -38,9 +39,12 @@ def test_no_fpu(self): thread = process.GetThreadAtIndex(0) self.assertTrue(thread.IsValid()) - self.assertEqual(thread.GetNumFrames(), 6) + # We have 4 named stack frames and two unnamed + # frames above that. The topmost two stack frames + # were not interesting for this test, so I didn't + # create symbols for them. + self.assertEqual(thread.GetNumFrames(), 3) stackframe_names = [ - "exception_catcher", "exception_catcher", "exception_thrower", "main", diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml b/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml index 9ce5ff49d9b6e..0b4e1f8fac3e2 100644 --- a/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml @@ -2,8 +2,8 @@ cpu: armv7m threads: - regsets: - flavor: gpr - registers: [{name: sp, value: 0x2000fe70}, {name: r7, value: 0x2000fe80}, - {name: pc, value: 0x0020392c}, {name: lr, value: 0x0020392d}] + registers: [{name: sp, value: 0x2000fe88}, {name: r7, value: 0x2000fe88}, + {name: pc, value: 0x00203916}, {name: lr, value: 0x0020392d}] memory-regions: # stack memory fetched via # (lldb) p/x $sp @@ -14,7 +14,7 @@ memory-regions: 0x0000002a, 0x20010e58, 0x00203923, 0x00000001, 0x2000fe88, 0x00203911, 0x2000ffdc, 0xfffffff9, 0x00000102, 0x00000002, 0x000003f0, 0x0000002a, - 0x20012620, 0x00203215, 0x00203366, 0x81000200, + 0x20012620, 0x00203215, 0x00202a92, 0x81000200, 0x00203215, 0x200128b0, 0x0024928d, 0x2000fecc, 0x002491ed, 0x20010e58, 0x20010e4c, 0x2000ffa0, 0x200107a0, 0x0000003c, 0x200116e8, 0x200108b0, @@ -62,3 +62,26 @@ memory-regions: 0x98, 0xae, 0x28, 0x00 ] + # exception_thrower + # (lldb) disass -b -c 12 -n exception_thrower + # 0x202a88 <+0>: 0xb5f0 push {r4, r5, r6, r7, lr} + # 0x202a8a <+2>: 0xaf03 add r7, sp, #0xc + # 0x202a8c <+4>: 0xe92d0f00 push.w {r8, r9, r10, r11} + # 0x202a90 <+8>: 0xb0c3 sub sp, #0x10c + # 0x202a92 <+10>: 0xf7ffffd9 bl 0x202a48 + - addr: 0x202a88 + UInt8: [ + 0xf0, 0xb5, 0x03, 0xaf, 0x2d, 0xe9, 0x00, 0x0f, + 0xc3, 0xb0, 0xff, 0xf7, 0xd9, 0xff, 0xff, 0xf7 + ] + + # main: + # 0x202a7e <+0>: push {r7, lr} + # 0x202a80 <+2>: mov r7, sp + # 0x202a82 <+4>: bl 0x202a88 ; exception_thrower + # 0x202a86 <+8>: nop + - addr: 0x202a7e + UInt8: [ + 0x80, 0xb5, 0x6f, 0x46, 0x00, 0xf0, 0x01, 0xf8, + 0x00, 0xbf + ] diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json b/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json index 8fcd5307ff82a..0de0169f7adb1 100644 --- a/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json @@ -1,5 +1,5 @@ { - "triple": "armv7m-apple", + "triple": "armv7m--", "uuid": "2D157DBA-53C9-3AC7-B5A1-9D336EC831CB", "type": "executable", "sections": [ @@ -28,13 +28,13 @@ { "name": "exception_catcher", "type": "code", - "size": 44, + "size": 32, "address": 2111760 }, { "name": "exception_thrower", "type": "code", - "size": 2652, + "size": 16, "address": 2108040 } ] From aab4ee46d72430361435eaca5eb0164da66100ee Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Mon, 13 Oct 2025 10:29:10 -0700 Subject: [PATCH 2/3] [lldb][API test] TestCortexMExceptionUnwind needs ARM llvm target Finally figured out the issue with TestCortexMExceptionUnwind.py failing on some CI. When the llvm is configured without the ARM target enabled, the ARM ABI plugins will not be initialized in lldb, and the unwind engine won't backtrace more than one stack frame. This test requires that the ARM target be enabled in the llvm cmake. Update the decorator to specify that; should be the end of these mysterious fails. (cherry picked from commit 8d29a3bb6f3d92d65bf5811b53bf42bf63685359) --- .../unwind/cortex-m-exception/TestCortexMExceptionUnwind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py index b40af33ae0179..741e59e85be77 100644 --- a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py @@ -11,7 +11,7 @@ class TestCortexMExceptionUnwind(TestBase): NO_DEBUG_INFO_TESTCASE = True - @skipIfRemote + @skipIfLLVMTargetMissing("ARM") def test_no_fpu(self): """Test that we can backtrace correctly through an ARM Cortex-M Exception return stack""" From 39dd4d3c87adb33d9ad627aec25c7e66df1136b4 Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Mon, 13 Oct 2025 18:49:47 -0700 Subject: [PATCH 3/3] [lldb] Remove obsolete comment in TestCortexMExceptionUnwind (cherry picked from commit 8706ef474c4bbc7cf7bf8895649789b96e07c842) --- .../unwind/cortex-m-exception/TestCortexMExceptionUnwind.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py index 741e59e85be77..bf7bc7ba33070 100644 --- a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py @@ -39,10 +39,6 @@ def test_no_fpu(self): thread = process.GetThreadAtIndex(0) self.assertTrue(thread.IsValid()) - # We have 4 named stack frames and two unnamed - # frames above that. The topmost two stack frames - # were not interesting for this test, so I didn't - # create symbols for them. self.assertEqual(thread.GetNumFrames(), 3) stackframe_names = [ "exception_catcher",