blob: 4f480e64a9dbd3356738a96a35cf8d710b3b3d46 [file] [log] [blame] [edit]
"""
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
@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,<addr> (disable) or Z0,<addr> (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),
)