diff options
| author | Mingming Liu <mingmingl@google.com> | 2025-09-10 15:25:31 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-10 15:25:31 -0700 |
| commit | 1417dafa1db9cb1b2b09438aa9f53ea5ab6e36e2 (patch) | |
| tree | 57f4b1f313c8cf74eed8819870f39c36ea263c68 /cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py | |
| parent | 898b813bc8a6d0276bf0f4769f5f2f64b34e632d (diff) | |
| parent | b8cefcb601ddaa18482555c4ff363c01a270c2fe (diff) | |
Merge branch 'main' into users/mingmingl-llvm/samplefdo-profile-formatusers/mingmingl-llvm/samplefdo-profile-format
Diffstat (limited to 'cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py')
| -rw-r--r-- | cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py | 158 |
1 files changed, 126 insertions, 32 deletions
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py index 06515dd8e0b1..4e64f880487f 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py @@ -218,6 +218,10 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): self.file_to_bp = defaultdict(list) # { dex_breakpoint_id -> (file, line, condition) } self.bp_info = {} + # { dex_breakpoint_id -> function_name } + self.function_bp_info = {} + # { dex_breakpoint_id -> instruction_reference } + self.instruction_bp_info = {} # We don't rely on IDs returned directly from the debug adapter. Instead, we use dexter breakpoint IDs, and # maintain a two-way-mapping of dex_bp_id<->dap_bp_id. This also allows us to defer the setting of breakpoints # in the debug adapter itself until necessary. @@ -226,6 +230,8 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): self.dex_id_to_dap_id = {} self.dap_id_to_dex_ids = {} self.pending_breakpoints: bool = False + self.pending_function_breakpoints: bool = False + self.pending_instruction_breakpoints: bool = False # List of breakpoints, indexed by BP ID # Each entry has the source file (for use in referencing desired_bps), and the DA-assigned # ID for that breakpoint if it has one (if it has been removed or not yet created then it will be None). @@ -288,6 +294,26 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): {"source": {"path": source}, "breakpoints": [bp.toDict() for bp in bps]}, ) + @staticmethod + def make_set_function_breakpoint_request(function_names: list) -> dict: + # Function breakpoints may specify conditions and hit counts, though we + # don't use those here (though perhaps we should use native hit count, + # rather than emulating it ConditionalController, now that we have a + # shared interface (DAP)). + return DAP.make_request( + "setFunctionBreakpoints", + {"breakpoints": [{"name": f} for f in function_names]}, + ) + + @staticmethod + def make_set_instruction_breakpoint_request(addrs: list) -> dict: + # Instruction breakpoints have additional fields we're ignoring for the + # moment. + return DAP.make_request( + "setInstructionBreakpoints", + {"breakpoints": [{"instructionReference": a} for a in addrs]}, + ) + ############################################################################ ## DAP communication & state-handling functions @@ -355,8 +381,6 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): else: pass elif message["type"] == "response": - request_seq = message["request_seq"] - debugger_state.set_response(request_seq, message) # TODO: We also receive a "continued" event, but it seems reasonable to set state based on either the # response or the event, since the DAP does not specify an order in which they are sent. May need revisiting # if there turns out to be some odd ordering issues, e.g. if we can receive messages in the order @@ -383,6 +407,10 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): body = message.get("body") if body: debugger_state.capabilities.update(logger, body) + # Now we've done whatever we need to do with the response, tell the + # receiver thread we've got it. + request_seq = message["request_seq"] + debugger_state.set_response(request_seq, message) def _colorize_dap_message(message: dict) -> dict: colorized_message = copy.deepcopy(message) @@ -569,6 +597,22 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): def _add_breakpoint(self, file, line): return self._add_conditional_breakpoint(file, line, None) + def add_function_breakpoint(self, name: str): + if not self._debugger_state.capabilities.supportsFunctionBreakpoints: + raise DebuggerException("Debugger does not support function breakpoints") + new_id = self.get_next_bp_id() + self.function_bp_info[new_id] = name + self.pending_function_breakpoints = True + return new_id + + def add_instruction_breakpoint(self, addr: str): + if not self._debugger_state.capabilities.supportsInstructionBreakpoints: + raise DebuggerException("Debugger does not support instruction breakpoints") + new_id = self.get_next_bp_id() + self.instruction_bp_info[new_id] = addr + self.pending_instruction_breakpoints = True + return new_id + def _add_conditional_breakpoint(self, file, line, condition): new_id = self.get_next_bp_id() self.file_to_bp[file].append(new_id) @@ -576,38 +620,73 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): self.pending_breakpoints = True return new_id + def _update_breakpoint_ids_after_request(self, dex_bp_ids: list, response: dict): + dap_bp_ids = [bp["id"] for bp in response["body"]["breakpoints"]] + if len(dex_bp_ids) != len(dap_bp_ids): + self.context.logger.error( + f"Sent request to set {len(dex_bp_ids)} breakpoints, but received {len(dap_bp_ids)} in response." + ) + visited_dap_ids = set() + for i, dex_bp_id in enumerate(dex_bp_ids): + dap_bp_id = dap_bp_ids[i] + self.dex_id_to_dap_id[dex_bp_id] = dap_bp_id + # We take the mappings in the response as the canonical mapping, meaning that if the debug server has + # simply *changed* the DAP ID for a breakpoint we overwrite the existing mapping rather than adding to + # it, but if we receive the same DAP ID for multiple Dex IDs *then* we store a one-to-many mapping. + if dap_bp_id in visited_dap_ids: + self.dap_id_to_dex_ids[dap_bp_id].append(dex_bp_id) + else: + self.dap_id_to_dex_ids[dap_bp_id] = [dex_bp_id] + visited_dap_ids.add(dap_bp_id) + def _flush_breakpoints(self): - if not self.pending_breakpoints: - return - for file in self.file_to_bp.keys(): - desired_bps = self._get_desired_bps(file) + # Normal and conditional breakpoints. + if self.pending_breakpoints: + self.pending_breakpoints = False + for file in self.file_to_bp.keys(): + desired_bps = self._get_desired_bps(file) + request_id = self.send_message( + self.make_set_breakpoint_request(file, desired_bps) + ) + result = self._await_response(request_id, 10) + if not result["success"]: + raise DebuggerException(f"could not set breakpoints for '{file}'") + # The debug adapter may have chosen to merge our breakpoints. From here we need to identify such cases and + # handle them so that our internal bookkeeping is correct. + dex_bp_ids = self.get_current_bps(file) + self._update_breakpoint_ids_after_request(dex_bp_ids, result) + + # Function breakpoints. + if self.pending_function_breakpoints: + self.pending_function_breakpoints = False + desired_bps = list(self.function_bp_info.values()) request_id = self.send_message( - self.make_set_breakpoint_request(file, desired_bps) + self.make_set_function_breakpoint_request(desired_bps) ) result = self._await_response(request_id, 10) if not result["success"]: - raise DebuggerException(f"could not set breakpoints for '{file}'") - # The debug adapter may have chosen to merge our breakpoints. From here we need to identify such cases and - # handle them so that our internal bookkeeping is correct. - dex_bp_ids = self.get_current_bps(file) - dap_bp_ids = [bp["id"] for bp in result["body"]["breakpoints"]] - if len(dex_bp_ids) != len(dap_bp_ids): - self.context.logger.error( - f"Sent request to set {len(dex_bp_ids)} breakpoints, but received {len(dap_bp_ids)} in response." + raise DebuggerException( + f"could not set function breakpoints: '{desired_bps}'" ) - visited_dap_ids = set() - for i, dex_bp_id in enumerate(dex_bp_ids): - dap_bp_id = dap_bp_ids[i] - self.dex_id_to_dap_id[dex_bp_id] = dap_bp_id - # We take the mappings in the response as the canonical mapping, meaning that if the debug server has - # simply *changed* the DAP ID for a breakpoint we overwrite the existing mapping rather than adding to - # it, but if we receive the same DAP ID for multiple Dex IDs *then* we store a one-to-many mapping. - if dap_bp_id in visited_dap_ids: - self.dap_id_to_dex_ids[dap_bp_id].append(dex_bp_id) - else: - self.dap_id_to_dex_ids[dap_bp_id] = [dex_bp_id] - visited_dap_ids.add(dap_bp_id) - self.pending_breakpoints = False + # We expect the breakpoint order to match in request and response. + dex_bp_ids = list(self.function_bp_info.keys()) + self._update_breakpoint_ids_after_request(dex_bp_ids, result) + + # Address / instruction breakpoints. + if self.pending_instruction_breakpoints: + self.pending_instruction_breakpoints = False + desired_bps = list(self.instruction_bp_info.values()) + request_id = self.send_message( + self.make_set_instruction_breakpoint_request(desired_bps) + ) + result = self._await_response(request_id, 10) + if not result["success"]: + raise DebuggerException( + f"could not set instruction breakpoints: '{desired_bps}'" + ) + # We expect the breakpoint order to match in request and response. + dex_bp_ids = list(self.instruction_bp_info.keys()) + self._update_breakpoint_ids_after_request(dex_bp_ids, result) def _confirm_triggered_breakpoint_ids(self, dex_bp_ids): """Can be overridden for any specific implementations that need further processing from the debug server's @@ -619,11 +698,12 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): # Breakpoints can only have been triggered if we've hit one. stop_reason = self._translate_stop_reason(self._debugger_state.stopped_reason) if stop_reason != StopReason.BREAKPOINT: - return [] + return set() breakpoint_ids = set( [ dex_id for dap_id in self._debugger_state.stopped_bps + if dap_id in self.dap_id_to_dex_ids for dex_id in self.dap_id_to_dex_ids[dap_id] ] ) @@ -632,8 +712,16 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): def delete_breakpoints(self, ids): per_file_deletions = defaultdict(list) for dex_bp_id in ids: - source, _, _ = self.bp_info[dex_bp_id] - per_file_deletions[source].append(dex_bp_id) + if dex_bp_id in self.bp_info: + source, _, _ = self.bp_info[dex_bp_id] + per_file_deletions[source].append(dex_bp_id) + elif dex_bp_id in self.function_bp_info: + del self.function_bp_info[dex_bp_id] + self.pending_function_breakpoints = True + elif dex_bp_id in self.instruction_bp_info: + del self.instruction_bp_info[dex_bp_id] + self.pending_instruction_breakpoints = True + for file, deleted_ids in per_file_deletions.items(): old_len = len(self.file_to_bp[file]) self.file_to_bp[file] = [ @@ -651,7 +739,13 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta): """ "Set the debugger-specific params used in a launch request.""" def launch(self, cmdline): - assert len(self.file_to_bp.keys()) > 0 + # FIXME: Should this be a warning or exception, rather than assert? + assert ( + len(self.file_to_bp) + + len(self.function_bp_info) + + len(self.instruction_bp_info) + > 0 + ), "Expected at least one breakpoint before launching" if self.context.options.target_run_args: cmdline += shlex.split(self.context.options.target_run_args) |
