"""Test SBThread.GetExtendedBacktraceThread API with queue debugging.""" import os import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * 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") # Get Xcode developer directory path. # Try DEVELOPER_DIR environment variable first, then fall back to xcode-select. xcode_dev_path = os.environ.get("DEVELOPER_DIR") if not xcode_dev_path: import subprocess xcode_dev_path = ( subprocess.check_output(["xcode-select", "-p"]).decode("utf-8").strip() ) # Check for libBacktraceRecording.dylib. libbtr_path = os.path.join( xcode_dev_path, "usr/lib/libBacktraceRecording.dylib" ) self.assertTrue( os.path.isfile(libbtr_path), f"libBacktraceRecording.dylib is not present at {libbtr_path}", ) 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)