| """Test SBThread.GetExtendedBacktraceThread API with queue debugging.""" | |
| import os | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test.lldbplatformutil import findBacktraceRecordingDylib | |
| from lldbsuite.test import lldbutil | |
| class TestExtendedBacktraceAPI(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| def setUp(self): | |
| TestBase.setUp(self) | |
| self.main_source = "main.m" | |
| @skipUnlessDarwin | |
| @add_test_categories(["objc", "pyapi"]) | |
| def test_extended_backtrace_thread_api(self): | |
| """Test GetExtendedBacktraceThread with queue debugging.""" | |
| self.build() | |
| exe = self.getBuildArtifact("a.out") | |
| libbtr_path = findBacktraceRecordingDylib() | |
| self.assertTrue( | |
| libbtr_path, | |
| "libBacktraceRecording.dylib was not found on the system.", | |
| ) | |
| self.assertTrue( | |
| os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"), | |
| "introspection libdispatch dylib not installed.", | |
| ) | |
| # Create launch info with environment variables for libBacktraceRecording. | |
| launch_info = lldb.SBLaunchInfo(None) | |
| launch_info.SetWorkingDirectory(self.get_process_working_directory()) | |
| launch_info.SetEnvironmentEntries( | |
| [ | |
| f"DYLD_INSERT_LIBRARIES={libbtr_path}", | |
| "DYLD_LIBRARY_PATH=/usr/lib/system/introspection", | |
| ], | |
| True, | |
| ) | |
| # Launch the process and run to breakpoint. | |
| target, process, thread, bp = lldbutil.run_to_name_breakpoint( | |
| self, "do_work_level_5", launch_info=launch_info, bkpt_module="a.out" | |
| ) | |
| self.assertTrue(target.IsValid(), VALID_TARGET) | |
| self.assertTrue(process.IsValid(), PROCESS_IS_VALID) | |
| self.assertTrue(thread.IsValid(), "Stopped thread is valid") | |
| self.assertTrue(bp.IsValid(), VALID_BREAKPOINT) | |
| # Call GetNumQueues to ensure queue information is loaded. | |
| num_queues = process.GetNumQueues() | |
| # Check that we can find the com.apple.main-thread queue. | |
| main_thread_queue_found = False | |
| for i in range(num_queues): | |
| queue = process.GetQueueAtIndex(i) | |
| if queue.GetName() == "com.apple.main-thread": | |
| main_thread_queue_found = True | |
| break | |
| # Verify we have at least 5 frames. | |
| self.assertGreaterEqual( | |
| thread.GetNumFrames(), | |
| 5, | |
| "Thread should have at least 5 frames in backtrace", | |
| ) | |
| # Get frame 2 BEFORE calling GetExtendedBacktraceThread. | |
| # This mimics what Xcode does - it has the frame objects ready. | |
| frame2 = thread.GetFrameAtIndex(2) | |
| self.assertTrue(frame2.IsValid(), "Frame 2 is valid") | |
| # Now test GetExtendedBacktraceThread. | |
| # This is the critical part - getting the extended backtrace calls into | |
| # libBacktraceRecording which does an inferior function call, and this | |
| # invalidates/clears the unwinder state. | |
| extended_thread = thread.GetExtendedBacktraceThread("libdispatch") | |
| # This should be valid since we injected libBacktraceRecording. | |
| self.assertTrue( | |
| extended_thread.IsValid(), | |
| "Extended backtrace thread for 'libdispatch' should be valid with libBacktraceRecording loaded", | |
| ) | |
| # The extended thread should have frames. | |
| self.assertGreater( | |
| extended_thread.GetNumFrames(), | |
| 0, | |
| "Extended backtrace thread should have at least one frame", | |
| ) | |
| # Test frame 2 on the extended backtrace thread. | |
| self.assertGreater( | |
| extended_thread.GetNumFrames(), | |
| 2, | |
| "Extended backtrace thread should have at least 3 frames to access frame 2", | |
| ) | |
| extended_frame2 = extended_thread.GetFrameAtIndex(2) | |
| self.assertTrue(extended_frame2.IsValid(), "Extended thread frame 2 is valid") | |
| # NOW try to access variables from frame 2 of the ORIGINAL thread. | |
| # This is the key test - after GetExtendedBacktraceThread() has executed | |
| # an inferior function call, the unwinder state may be invalidated. | |
| # Xcode exhibits this bug where variables show "register fp is not available" | |
| # after extended backtrace retrieval. | |
| # Set frame 2 as the selected frame so expect_var_path works. | |
| thread.SetSelectedFrame(2) | |
| variables = frame2.GetVariables(False, True, False, True) | |
| self.assertGreater( | |
| variables.GetSize(), 0, "Frame 2 should have at least one variable" | |
| ) | |
| # Test all variables in frame 2, like Xcode does. | |
| # Use expect_var_path to verify each variable is accessible without errors. | |
| for i in range(variables.GetSize()): | |
| var = variables.GetValueAtIndex(i) | |
| var_name = var.GetName() | |
| # This will fail if the variable contains "not available" or has errors. | |
| self.expect_var_path(var_name) |