""" Test that the batched breakpoint step-over optimization activates when multiple threads hit the same breakpoint. Verifies that the optimization reduces breakpoint toggle operations compared to stepping one at a time. """ import os import re from lldbsuite.test.decorators import * from lldbsuite.test.concurrent_base import ConcurrentEventsBase from lldbsuite.test.lldbtest import TestBase @skipIfTargetDoesNotSupportThreads() @skipIfWindows class ConcurrentBatchedBreakpointStepOver(ConcurrentEventsBase): @skipIf(triple="^mips") @skipIf(archs=["aarch64"]) def test(self): """Test that batched breakpoint step-over reduces breakpoint toggle operations when multiple threads hit the same breakpoint.""" self.build() num_threads = 10 # Enable logging to capture optimization messages and GDB packets. lldb_logfile = self.getBuildArtifact("lldb-log.txt") self.runCmd("log enable lldb step break -f {}".format(lldb_logfile)) gdb_logfile = self.getBuildArtifact("gdb-remote-log.txt") self.runCmd("log enable gdb-remote packets -f {}".format(gdb_logfile)) # Run with breakpoint threads. self.do_thread_actions(num_breakpoint_threads=num_threads) self.assertTrue(os.path.isfile(lldb_logfile), "lldb log file not found") with open(lldb_logfile, "r") as f: lldb_log = f.read() # Verify the optimization activated by looking for "Registered thread" # messages, which indicate threads were grouped for batching. registered_matches = re.findall( r"Registered thread 0x[0-9a-fA-F]+ stepping over " r"breakpoint at (0x[0-9a-fA-F]+)", lldb_log, ) self.assertGreater( len(registered_matches), 0, "Expected batched breakpoint step-over optimization to be " "used (no 'Registered thread' messages found in log).", ) thread_bp_addr = registered_matches[0] # Verify all threads completed their step-over. completed_count = lldb_log.count("Completed step over breakpoint plan.") self.assertGreaterEqual( completed_count, num_threads, "Expected at least {} 'Completed step over breakpoint plan.' " "messages (one per thread), but got {}.".format( num_threads, completed_count ), ) # Count z0/Z0 packets for the thread breakpoint address. # z0 = remove (disable) software breakpoint. # Z0 = set (enable) software breakpoint. # Strip the "0x" prefix and leading zeros to match the GDB packet # format (which uses lowercase hex without "0x" prefix). bp_addr_hex = thread_bp_addr[2:].lstrip("0") if thread_bp_addr else "" z0_count = 0 # disable packets Z0_count = 0 # enable packets initial_Z0_seen = False max_vcont_step_threads = 0 # largest number of s: actions in one vCont self.assertTrue(os.path.isfile(gdb_logfile), "gdb-remote log file not found") with open(gdb_logfile, "r") as f: for line in f: if "send packet: $" not in line: continue # Match z0, (disable) or Z0, (enable). m = re.search(r"send packet: \$([Zz])0,([0-9a-fA-F]+),", line) if m and m.group(2) == bp_addr_hex: if m.group(1) == "Z": if not initial_Z0_seen: initial_Z0_seen = True else: Z0_count += 1 else: z0_count += 1 # Count step actions in vCont packets to detect batching. # A batched vCont looks like: vCont;s:tid1;s:tid2;... vcont_m = re.search(r"send packet: \$vCont((?:;[^#]+)*)", line) if vcont_m: actions = vcont_m.group(1) step_count = len(re.findall(r";s:", actions)) if step_count > max_vcont_step_threads: max_vcont_step_threads = step_count # With the optimization, fewer breakpoint toggles should occur. # Without optimization we'd see num_threads z0 and num_threads Z0. # With batching, even partial, we expect fewer toggles. self.assertLess( z0_count, num_threads, "Expected fewer than {} breakpoint disables (z0) due to " "batching, but got {}.".format(num_threads, z0_count), ) self.assertLess( Z0_count, num_threads, "Expected fewer than {} breakpoint re-enables (Z0) due to " "batching, but got {}.".format(num_threads, Z0_count), ) # Verify at least one batched vCont packet contained multiple # step actions, proving threads were stepped together. self.assertGreater( max_vcont_step_threads, 1, "Expected at least one batched vCont packet with multiple " "step actions (s:), but the maximum was {}.".format(max_vcont_step_threads), )