| """ | |
| Multiple threads stop at a code address where there is a breakpoint | |
| at the same time. Some threads have executed the breakpoint | |
| instruction and have a stop reason. Other threads have not yet hit | |
| the breakpoint instruction. When lldb resumes execution, only the | |
| threads that have hit the breakpoint should instruction-step past | |
| the breakpoint before resuming. | |
| """ | |
| import re | |
| import json | |
| import lldb | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.gdbclientutils import * | |
| from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase | |
| class TestBreakpointNotHit(GDBRemoteTestBase): | |
| @skipIfXmlSupportMissing | |
| def test(self): | |
| BP_ADDR = 0x1004 | |
| NUM_THREADS = 4 | |
| class MyResponder(MockGDBServerResponder): | |
| def __init__(self): | |
| MockGDBServerResponder.__init__(self) | |
| self.bp_addr = BP_ADDR | |
| self.tids = [0x101 + i for i in range(NUM_THREADS)] | |
| self.pcs = [0x1000 for _ in range(NUM_THREADS)] | |
| self.selected_thread = 0x101 | |
| self.breakpoints = set() | |
| self.second_stop_at_bp_addr = False | |
| # Threads that have just hit a breakpoint. | |
| self.tids_hit_bp = [] | |
| # Threads that have just completed an instruction step. | |
| self.tids_completed_stepi = [] | |
| def qSupported(self, client_supported): | |
| return ( | |
| "PacketSize=3fff;QStartNoAckMode+;" | |
| "qXfer:features:read+;swbreak+;hwbreak+" | |
| ) | |
| def qXferRead(self, obj, annex, offset, length): | |
| if annex == "target.xml": | |
| return ( | |
| """<?xml version="1.0"?> | |
| <target version="1.0"> | |
| <feature name="com.apple.debugserver.arm64"> | |
| <reg name="fp" regnum="29" bitsize="64" group="general" generic="fp"/> | |
| <reg name="lr" regnum="30" bitsize="64" group="general" generic="ra"/> | |
| <reg name="sp" regnum="31" bitsize="64" group="general" generic="sp"/> | |
| <reg name="pc" regnum="32" bitsize="64" group="general" generic="pc"/> | |
| </feature> | |
| </target>""", | |
| False, | |
| ) | |
| return None, False | |
| def cont(self): | |
| # Clear the tid states. | |
| self.tids_completed_stepi = [] | |
| self.tids_hit_bp = [] | |
| new_pcs = [] | |
| for idx, tid in enumerate(self.tids): | |
| pc = self.pcs[idx] | |
| # Thread 0x104 | |
| # (1) first stops at BP_ADDR but hasn't hit it. | |
| # then on next continue, | |
| # (2) hits the breakpoint at BP_ADDR (pc does not advance). | |
| if ( | |
| tid == 0x104 | |
| and pc == BP_ADDR | |
| and not self.second_stop_at_bp_addr | |
| ): | |
| self.tids_hit_bp.append(tid) | |
| self.second_stop_at_bp_addr = True | |
| new_pcs.append(pc) | |
| else: | |
| new_pcs.append(pc + 4) | |
| if (pc + 4) == BP_ADDR and tid != 0x104: | |
| self.tids_hit_bp.append(tid) | |
| self.pcs = new_pcs | |
| return self.haltReason() | |
| def setBreakpoint(self, packet): | |
| bp_data = packet[1:].split(",") | |
| self.breakpoints.add(int(bp_data[1], 16)) | |
| return "OK" | |
| def clearBreakpoint(self, packet): | |
| bp_data = packet[1:].split(",") | |
| self.breakpoints.remove(int(bp_data[1], 16)) | |
| return "OK" | |
| def qHostInfo(self): | |
| return "cputype:16777228;cpusubtype:2;addressing_bits:47;ostype:macosx;watchpoint_exceptions_received:before;vendor:apple;os_version:27.0.0;triple:61726D36342D6170706C652D6D61636F7378;" | |
| def haltReason(self): | |
| threads_str = ",".join("{:x}".format(t) for t in self.tids) | |
| pcs_str = ",".join("{:x}".format(p) for p in self.pcs) | |
| jstopinfos = [] | |
| for idx, tid in enumerate(self.tids): | |
| if tid in self.tids_completed_stepi or tid in self.tids_hit_bp: | |
| medata = 0 | |
| if tid in self.tids_hit_bp: | |
| medata = self.pcs[idx] | |
| jstopinfos.append( | |
| '{"tid":%d,"metype":6,"medata":[1,%d],"reason":"exception"}' | |
| % (tid, medata) | |
| ) | |
| response = "T05thread:{:x};threads:{};thread-pcs:{};".format( | |
| self.tids[0], threads_str, pcs_str | |
| ) | |
| if len(jstopinfos) > 0: | |
| json_str = "[%s]" % ",".join(jstopinfos) | |
| response += "jstopinfo:%s;" % json_str.encode().hex() | |
| if self.tids[0] in self.tids_hit_bp: | |
| response += "metype:6;mecount:2;medata:1;medata:%x;" % (self.pcs[0]) | |
| else: | |
| if self.tids[0] in self.tids_completed_stepi: | |
| response += "metype:6;mecount:2;medata:1;medata:0;" | |
| response += "20:%016x;" % (self.swap64(self.pcs[0])) | |
| response += "1d:0000000000000000;" | |
| response += "1f:0000000000000000;" | |
| return response | |
| # Register values need to be sent in native-endian (little) | |
| def swap64(self, x): | |
| return ( | |
| ((x << 56) & 0xFF00000000000000) | |
| | ((x << 40) & 0x00FF000000000000) | |
| | ((x << 24) & 0x0000FF0000000000) | |
| | ((x << 8) & 0x000000FF00000000) | |
| | ((x >> 8) & 0x00000000FF000000) | |
| | ((x >> 24) & 0x0000000000FF0000) | |
| | ((x >> 40) & 0x000000000000FF00) | |
| | ((x >> 56) & 0x00000000000000FF) | |
| ) | |
| def jThreadsInfo(self): | |
| response_array = [] | |
| for idx, tid in enumerate(self.tids): | |
| this = {} | |
| this["tid"] = tid | |
| if tid in self.tids_hit_bp: | |
| this["metype"] = 6 | |
| this["medata"] = [1, self.pcs[idx]] | |
| this["reason"] = "exception" | |
| if tid in self.tids_completed_stepi: | |
| this["metype"] = 6 | |
| this["medata"] = [1, 0] | |
| this["reason"] = "exception" | |
| this["registers"] = { | |
| "20": "%016x" % (self.swap64(self.pcs[idx])), | |
| "1d": "0000000000000000", | |
| "1f": "0000000000000000", | |
| } | |
| response_array.append(this) | |
| return json.dumps(response_array, indent=2) | |
| def _handle_vCont(self, packet): | |
| self.tids_completed_stepi = [] | |
| stepping_tids = [] | |
| tids_hit_a_bp = [] | |
| # Parse step actions from vCont. | |
| for action in packet[6:].split(";"): | |
| if not action: | |
| continue | |
| if action.startswith("s:"): | |
| tid_str = action[2:] | |
| if "." in tid_str: | |
| tid_str = tid_str.split(".")[1] | |
| stepping_tids.append(int(tid_str, 16)) | |
| for idx, tid in enumerate(self.tids): | |
| # If a thread is _at_ a breakpoint instruction | |
| # but hasn't hit yet it, mark it as a breakpoint | |
| # hit and don't advance. | |
| if tid in stepping_tids: | |
| if ( | |
| self.pcs[idx] in self.breakpoints | |
| and tid not in self.tids_hit_bp | |
| ): | |
| tids_hit_a_bp.append(tid) | |
| else: | |
| self.pcs[idx] += 4 | |
| self.tids_completed_stepi.append(tid) | |
| self.tids_hit_bp = tids_hit_a_bp | |
| return self.haltReason() | |
| def readRegisters(self): | |
| return "00" * (8 * 4) | |
| def readRegister(self, regno): | |
| if self.selected_thread in self.tids and regno == 32: | |
| for idx, tid in enumerate(self.tids): | |
| if tid == self.selected_thread: | |
| return "%016x" % self.swap64(self.pcs[idx]) | |
| return "00" * 8 | |
| def selectThread(self, op, thread_id): | |
| self.selected_thread = thread_id | |
| return "OK" | |
| def other(self, packet): | |
| if packet == "vCont?": | |
| return "vCont;c;C;s;S" | |
| if packet.startswith("vCont;"): | |
| return self._handle_vCont(packet) | |
| return "" | |
| self.server.responder = MyResponder() | |
| if self.TraceOn(): | |
| self.runCmd("log enable gdb-remote packets") | |
| # self.runCmd("log enable -v lldb break") | |
| # self.runCmd("log enable -v lldb step") | |
| # self.runCmd("log enable -v lldb temp") | |
| target = self.dbg.CreateTarget("") | |
| process = self.connect(target) | |
| self.assertEqual(process.GetNumThreads(), NUM_THREADS) | |
| bkpt = target.BreakpointCreateByAddress(BP_ADDR) | |
| self.assertTrue(bkpt.IsValid()) | |
| self.runCmd("break list") | |
| self.runCmd("thread list") | |
| # Continue to the breakpoint hit on 3 threads; not on 4th | |
| process.Continue() | |
| self.runCmd("break list") | |
| self.runCmd("thread list") | |
| self.assertEqual(bkpt.GetHitCount(), 3) | |
| # Continue the process, which must instruction step | |
| # the three threads that have hit the breakpoint, then | |
| # resume all threads (and the 4th thread will hit the breakpoint) | |
| process.Continue() | |
| self.runCmd("break list") | |
| self.runCmd("thread list") | |
| self.assertEqual(bkpt.GetHitCount(), 4) |