| """Test queues inspection SB APIs.""" |
| |
| import os |
| import lldb |
| from lldbsuite.test.decorators import * |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test import lldbutil |
| |
| |
| class TestQueues(TestBase): |
| @skipUnlessDarwin |
| @add_test_categories(["pyapi"]) |
| def test_with_python_api_queues(self): |
| """Test queues inspection SB APIs.""" |
| self.build() |
| self.queues() |
| |
| @skipUnlessDarwin |
| @add_test_categories(["pyapi"]) |
| def test_queue_specific_breakpoints(self): |
| self.build() |
| self.queue_specific_breakpoints() |
| |
| @skipUnlessDarwin |
| @add_test_categories(["pyapi"]) |
| def test_with_python_api_queues_with_backtrace(self): |
| """Test queues inspection SB APIs.""" |
| self.build() |
| self.queues_with_libBacktraceRecording() |
| |
| def setUp(self): |
| # Call super's setUp(). |
| TestBase.setUp(self) |
| # Find the line numbers that we will step to in main: |
| self.main_source = "main.c" |
| |
| def check_queue_for_valid_queue_id(self, queue): |
| self.assertTrue( |
| queue.GetQueueID() != 0, |
| "Check queue %s for valid QueueID (got 0x%x)" |
| % (queue.GetName(), queue.GetQueueID()), |
| ) |
| |
| def check_running_and_pending_items_on_queue( |
| self, queue, expected_running, expected_pending |
| ): |
| self.assertEqual( |
| queue.GetNumPendingItems(), |
| expected_pending, |
| "queue %s should have %d pending items, instead has %d pending items" |
| % (queue.GetName(), expected_pending, (queue.GetNumPendingItems())), |
| ) |
| self.assertEqual( |
| queue.GetNumRunningItems(), |
| expected_running, |
| "queue %s should have %d running items, instead has %d running items" |
| % (queue.GetName(), expected_running, (queue.GetNumRunningItems())), |
| ) |
| |
| def describe_threads(self): |
| desc = [] |
| for x in self.inferior_process: |
| id = x.GetIndexID() |
| reason_str = lldbutil.stop_reason_to_str(x.GetStopReason()) |
| |
| location = "\t".join( |
| [ |
| lldbutil.get_description(x.GetFrameAtIndex(i)) |
| for i in range(x.GetNumFrames()) |
| ] |
| ) |
| desc.append( |
| "thread %d: %s (queue id: %s) at\n\t%s" |
| % (id, reason_str, x.GetQueueID(), location) |
| ) |
| print("\n".join(desc)) |
| |
| def check_number_of_threads_owned_by_queue(self, queue, number_threads): |
| if queue.GetNumThreads() != number_threads: |
| self.describe_threads() |
| |
| self.assertEqual( |
| queue.GetNumThreads(), |
| number_threads, |
| "queue %s should have %d thread executing, but has %d" |
| % (queue.GetName(), number_threads, queue.GetNumThreads()), |
| ) |
| |
| def check_queue_kind(self, queue, kind): |
| expected_kind_string = "Unknown" |
| if kind == lldb.eQueueKindSerial: |
| expected_kind_string = "Serial queue" |
| if kind == lldb.eQueueKindConcurrent: |
| expected_kind_string = "Concurrent queue" |
| actual_kind_string = "Unknown" |
| if queue.GetKind() == lldb.eQueueKindSerial: |
| actual_kind_string = "Serial queue" |
| if queue.GetKind() == lldb.eQueueKindConcurrent: |
| actual_kind_string = "Concurrent queue" |
| self.assertEqual( |
| queue.GetKind(), |
| kind, |
| "queue %s is expected to be a %s but it is actually a %s" |
| % (queue.GetName(), expected_kind_string, actual_kind_string), |
| ) |
| |
| def check_queues_threads_match_queue(self, queue): |
| for idx in range(0, queue.GetNumThreads()): |
| t = queue.GetThreadAtIndex(idx) |
| self.assertTrue( |
| t.IsValid(), |
| "Queue %s's thread #%d must be valid" % (queue.GetName(), idx), |
| ) |
| self.assertEqual( |
| t.GetQueueID(), |
| queue.GetQueueID(), |
| "Queue %s has a QueueID of %d but its thread #%d has a QueueID of %d" |
| % (queue.GetName(), queue.GetQueueID(), idx, t.GetQueueID()), |
| ) |
| self.assertEqual( |
| t.GetQueueName(), |
| queue.GetName(), |
| "Queue %s has a QueueName of %s but its thread #%d has a QueueName of %s" |
| % (queue.GetName(), queue.GetName(), idx, t.GetQueueName()), |
| ) |
| self.assertEqual( |
| t.GetQueue().GetQueueID(), |
| queue.GetQueueID(), |
| "Thread #%d's Queue's QueueID of %d is not the same as the QueueID of its owning queue %d" |
| % (idx, t.GetQueue().GetQueueID(), queue.GetQueueID()), |
| ) |
| |
| def check_queue_breakpoints(self, queue1, queue2, queue_breakpoint): |
| queue1_thread = queue1.GetThreadAtIndex(0) |
| queue2_thread = queue2.GetThreadAtIndex(0) |
| |
| self.assertEqual( |
| queue_breakpoint.GetQueueName(), |
| queue1.GetName(), |
| "The breakpoint was set for queue %s, but the breakpoint's queue name is %s" |
| % (queue_breakpoint.GetQueueName(), queue1.GetName()), |
| ) |
| self.assertEqual( |
| queue_breakpoint.GetHitCount(), |
| 1, |
| "The breakpoint for queue %s has not been hit" |
| % (queue_breakpoint.GetQueueName()), |
| ) |
| self.assertStopReason( |
| queue1_thread.GetStopReason(), |
| lldb.eStopReasonBreakpoint, |
| "Queue %s is not stopped at breakpoint %d" |
| % (queue1.GetName(), queue_breakpoint.GetID()), |
| ) |
| self.assertNotEqual( |
| queue2_thread.GetStopReason(), |
| lldb.eStopReasonBreakpoint, |
| "Queue %s is stopped at breakpoint %d, but this breakpoint should only be hit for queue %s" |
| % ( |
| queue2.GetName(), |
| queue_breakpoint.GetID(), |
| queue_breakpoint.GetQueueName(), |
| ), |
| ) |
| |
| def queues(self): |
| """Test queues inspection SB APIs without libBacktraceRecording.""" |
| exe = self.getBuildArtifact("a.out") |
| |
| target = self.dbg.CreateTarget(exe) |
| self.assertTrue(target, VALID_TARGET) |
| self.main_source_spec = lldb.SBFileSpec(self.main_source) |
| break1 = target.BreakpointCreateByName("stopper", "a.out") |
| self.assertTrue(break1, VALID_BREAKPOINT) |
| process = target.LaunchSimple([], None, self.get_process_working_directory()) |
| self.assertTrue(process, PROCESS_IS_VALID) |
| threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1) |
| if len(threads) != 1: |
| self.fail("Failed to stop at breakpoint 1.") |
| |
| self.inferior_process = process |
| |
| queue_submittor_1 = lldb.SBQueue() |
| queue_performer_1 = lldb.SBQueue() |
| queue_performer_2 = lldb.SBQueue() |
| queue_performer_3 = lldb.SBQueue() |
| for idx in range(0, process.GetNumQueues()): |
| q = process.GetQueueAtIndex(idx) |
| if q.GetName() == "com.apple.work_submittor_1": |
| queue_submittor_1 = q |
| if q.GetName() == "com.apple.work_performer_1": |
| queue_performer_1 = q |
| if q.GetName() == "com.apple.work_performer_2": |
| queue_performer_2 = q |
| if q.GetName() == "com.apple.work_performer_3": |
| queue_performer_3 = q |
| |
| self.assertTrue( |
| queue_submittor_1.IsValid() |
| and queue_performer_1.IsValid() |
| and queue_performer_2.IsValid() |
| and queue_performer_3.IsValid(), |
| "Got all four expected queues: %s %s %s %s" |
| % ( |
| queue_submittor_1.IsValid(), |
| queue_performer_1.IsValid(), |
| queue_performer_2.IsValid(), |
| queue_performer_3.IsValid(), |
| ), |
| ) |
| |
| self.check_queue_for_valid_queue_id(queue_submittor_1) |
| self.check_queue_for_valid_queue_id(queue_performer_1) |
| self.check_queue_for_valid_queue_id(queue_performer_2) |
| self.check_queue_for_valid_queue_id(queue_performer_3) |
| |
| self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_1, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_2, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_3, 4) |
| |
| self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent) |
| |
| self.check_queues_threads_match_queue(queue_submittor_1) |
| self.check_queues_threads_match_queue(queue_performer_1) |
| self.check_queues_threads_match_queue(queue_performer_2) |
| self.check_queues_threads_match_queue(queue_performer_3) |
| |
| # We have threads running with all the different dispatch QoS service |
| # levels - find those threads and check that we can get the correct |
| # QoS name for each of them. |
| |
| user_initiated_thread = lldb.SBThread() |
| user_interactive_thread = lldb.SBThread() |
| utility_thread = lldb.SBThread() |
| background_thread = lldb.SBThread() |
| for th in process.threads: |
| if th.GetName() == "user initiated QoS": |
| user_initiated_thread = th |
| if th.GetName() == "user interactive QoS": |
| user_interactive_thread = th |
| if th.GetName() == "utility QoS": |
| utility_thread = th |
| if th.GetName() == "background QoS": |
| background_thread = th |
| |
| self.assertTrue( |
| user_initiated_thread.IsValid(), "Found user initiated QoS thread" |
| ) |
| self.assertTrue( |
| user_interactive_thread.IsValid(), "Found user interactive QoS thread" |
| ) |
| self.assertTrue(utility_thread.IsValid(), "Found utility QoS thread") |
| self.assertTrue(background_thread.IsValid(), "Found background QoS thread") |
| |
| stream = lldb.SBStream() |
| self.assertTrue( |
| user_initiated_thread.GetInfoItemByPathAsString( |
| "requested_qos.printable_name", stream |
| ), |
| "Get QoS printable string for user initiated QoS thread", |
| ) |
| self.assertEqual( |
| stream.GetData(), |
| "User Initiated", |
| "user initiated QoS thread name is valid", |
| ) |
| stream.Clear() |
| self.assertTrue( |
| user_interactive_thread.GetInfoItemByPathAsString( |
| "requested_qos.printable_name", stream |
| ), |
| "Get QoS printable string for user interactive QoS thread", |
| ) |
| self.assertEqual( |
| stream.GetData(), |
| "User Interactive", |
| "user interactive QoS thread name is valid", |
| ) |
| stream.Clear() |
| self.assertTrue( |
| utility_thread.GetInfoItemByPathAsString( |
| "requested_qos.printable_name", stream |
| ), |
| "Get QoS printable string for utility QoS thread", |
| ) |
| self.assertEqual( |
| stream.GetData(), "Utility", "utility QoS thread name is valid" |
| ) |
| stream.Clear() |
| self.assertTrue( |
| background_thread.GetInfoItemByPathAsString( |
| "requested_qos.printable_name", stream |
| ), |
| "Get QoS printable string for background QoS thread", |
| ) |
| self.assertEqual( |
| stream.GetData(), "Background", "background QoS thread name is valid" |
| ) |
| |
| @skipIfDarwin # rdar://50379398 |
| def queues_with_libBacktraceRecording(self): |
| """Test queues inspection SB APIs with libBacktraceRecording present.""" |
| exe = self.getBuildArtifact("a.out") |
| |
| if not os.path.isfile( |
| "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib" |
| ): |
| self.skipTest( |
| "Skipped because libBacktraceRecording.dylib was present on the system." |
| ) |
| |
| if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"): |
| self.skipTest( |
| "Skipped because introspection libdispatch dylib is not present." |
| ) |
| |
| target = self.dbg.CreateTarget(exe) |
| self.assertTrue(target, VALID_TARGET) |
| |
| self.main_source_spec = lldb.SBFileSpec(self.main_source) |
| |
| break1 = target.BreakpointCreateByName("stopper", "a.out") |
| self.assertTrue(break1, VALID_BREAKPOINT) |
| |
| # Now launch the process, and do not stop at entry point. |
| libbtr_path = "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib" |
| if self.getArchitecture() in [ |
| "arm", |
| "arm64", |
| "arm64e", |
| "arm64_32", |
| "armv7", |
| "armv7k", |
| ]: |
| libbtr_path = "/Developer/usr/lib/libBacktraceRecording.dylib" |
| |
| process = target.LaunchSimple( |
| [], |
| [ |
| "DYLD_INSERT_LIBRARIES=%s" % (libbtr_path), |
| "DYLD_LIBRARY_PATH=/usr/lib/system/introspection", |
| ], |
| self.get_process_working_directory(), |
| ) |
| |
| self.assertTrue(process, PROCESS_IS_VALID) |
| |
| # The stop reason of the thread should be breakpoint. |
| threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1) |
| if len(threads) != 1: |
| self.fail("Failed to stop at breakpoint 1.") |
| |
| self.inferior_process = process |
| |
| libbtr_module_filespec = lldb.SBFileSpec("libBacktraceRecording.dylib") |
| libbtr_module = target.FindModule(libbtr_module_filespec) |
| if not libbtr_module.IsValid(): |
| self.skipTest( |
| "Skipped because libBacktraceRecording.dylib was not loaded into the process." |
| ) |
| |
| self.assertTrue( |
| process.GetNumQueues() >= 4, "Found the correct number of queues." |
| ) |
| |
| queue_submittor_1 = lldb.SBQueue() |
| queue_performer_1 = lldb.SBQueue() |
| queue_performer_2 = lldb.SBQueue() |
| queue_performer_3 = lldb.SBQueue() |
| for idx in range(0, process.GetNumQueues()): |
| q = process.GetQueueAtIndex(idx) |
| if "LLDB_COMMAND_TRACE" in os.environ: |
| print("Queue with id %s has name %s" % (q.GetQueueID(), q.GetName())) |
| if q.GetName() == "com.apple.work_submittor_1": |
| queue_submittor_1 = q |
| if q.GetName() == "com.apple.work_performer_1": |
| queue_performer_1 = q |
| if q.GetName() == "com.apple.work_performer_2": |
| queue_performer_2 = q |
| if q.GetName() == "com.apple.work_performer_3": |
| queue_performer_3 = q |
| if q.GetName() == "com.apple.main-thread": |
| if q.GetNumThreads() == 0: |
| print("Cannot get thread <=> queue associations") |
| return |
| |
| self.assertTrue( |
| queue_submittor_1.IsValid() |
| and queue_performer_1.IsValid() |
| and queue_performer_2.IsValid() |
| and queue_performer_3.IsValid(), |
| "Got all four expected queues: %s %s %s %s" |
| % ( |
| queue_submittor_1.IsValid(), |
| queue_performer_1.IsValid(), |
| queue_performer_2.IsValid(), |
| queue_performer_3.IsValid(), |
| ), |
| ) |
| |
| self.check_queue_for_valid_queue_id(queue_submittor_1) |
| self.check_queue_for_valid_queue_id(queue_performer_1) |
| self.check_queue_for_valid_queue_id(queue_performer_2) |
| self.check_queue_for_valid_queue_id(queue_performer_3) |
| |
| self.check_running_and_pending_items_on_queue(queue_submittor_1, 1, 0) |
| self.check_running_and_pending_items_on_queue(queue_performer_1, 1, 3) |
| self.check_running_and_pending_items_on_queue(queue_performer_2, 1, 9999) |
| self.check_running_and_pending_items_on_queue(queue_performer_3, 4, 0) |
| |
| self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_1, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_2, 1) |
| self.check_number_of_threads_owned_by_queue(queue_performer_3, 4) |
| |
| self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial) |
| self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent) |
| |
| self.check_queues_threads_match_queue(queue_submittor_1) |
| self.check_queues_threads_match_queue(queue_performer_1) |
| self.check_queues_threads_match_queue(queue_performer_2) |
| self.check_queues_threads_match_queue(queue_performer_3) |
| |
| self.assertTrue( |
| queue_performer_2.GetPendingItemAtIndex(0).IsValid(), |
| "queue 2's pending item #0 is valid", |
| ) |
| self.assertEqual( |
| queue_performer_2.GetPendingItemAtIndex(0) |
| .GetAddress() |
| .GetSymbol() |
| .GetName(), |
| "doing_the_work_2", |
| "queue 2's pending item #0 should be doing_the_work_2", |
| ) |
| self.assertEqual( |
| queue_performer_2.GetNumPendingItems(), |
| 9999, |
| "verify that queue 2 still has 9999 pending items", |
| ) |
| self.assertTrue( |
| queue_performer_2.GetPendingItemAtIndex(9998).IsValid(), |
| "queue 2's pending item #9998 is valid", |
| ) |
| self.assertEqual( |
| queue_performer_2.GetPendingItemAtIndex(9998) |
| .GetAddress() |
| .GetSymbol() |
| .GetName(), |
| "doing_the_work_2", |
| "queue 2's pending item #0 should be doing_the_work_2", |
| ) |
| self.assertTrue( |
| queue_performer_2.GetPendingItemAtIndex(9999).IsValid() == False, |
| "queue 2's pending item #9999 is invalid", |
| ) |
| |
| def queue_specific_breakpoints(self): |
| # Run the executable until the stopper function and get the breakpoint |
| # that's created from that. Then set the queue name of the breakpoint |
| # to be the name of the main thread |
| ( |
| target, |
| process, |
| main_thread, |
| queue_breakpoint, |
| ) = lldbutil.run_to_name_breakpoint(self, "stopper", only_one_thread=False) |
| queue_breakpoint.SetQueueName(main_thread.GetQueue().GetName()) |
| |
| # Create a submittor queue |
| queue_submittor_1 = lldb.SBQueue() |
| for idx in range(0, process.GetNumQueues()): |
| q = process.GetQueueAtIndex(idx) |
| if q.GetName() == "com.apple.work_submittor_1": |
| queue_submittor_1 = q |
| |
| self.assertTrue( |
| queue_submittor_1.IsValid(), |
| "Unable to get expected queue com.apple.work_submittor_1, instead got queue %s" |
| % (queue_submittor_1.GetName()), |
| ) |
| |
| self.check_queue_breakpoints( |
| main_thread.GetQueue(), queue_submittor_1, queue_breakpoint |
| ) |