diff options
Diffstat (limited to 'cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py')
| -rw-r--r-- | cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py index e8bc65cd3fbe..063df770467e 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py @@ -13,6 +13,7 @@ from subprocess import CalledProcessError, check_output, STDOUT import sys from dex.debugger.DebuggerBase import DebuggerBase, watch_is_active +from dex.debugger.DAP import DAP from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR from dex.dextIR import StackFrame, SourceLocation, ProgramState from dex.utils.Exceptions import DebuggerException, LoadDebuggerException @@ -382,3 +383,161 @@ class LLDB(DebuggerBase): is_optimized_away=is_optimized_away, is_irretrievable=is_irretrievable, ) + + +class LLDBDAP(DAP): + def __init__(self, context, *args): + self.lldb_dap_executable = context.options.lldb_executable + super(LLDBDAP, self).__init__(context, *args) + + @classmethod + def get_name(cls): + return "lldb-dap" + + @classmethod + def get_option_name(cls): + return "lldb-dap" + + @property + def version(self): + return 1 + + @property + def _debug_adapter_name(self) -> str: + return "lldb-dap" + + @property + def _debug_adapter_executable(self) -> str: + return self.lldb_dap_executable + + @property + def frames_below_main(self): + return [ + "__scrt_common_main_seh", + "__libc_start_main", + "__libc_start_call_main", + "_start", + ] + + def _post_step_hook(self): + """Hook to be executed after completing a step request.""" + if self._debugger_state.stopped_reason == "step": + trace_req_id = self.send_message( + self.make_request( + "stackTrace", {"threadId": self._debugger_state.thread, "levels": 1} + ) + ) + trace_response = self._await_response(trace_req_id) + if not trace_response["success"]: + raise DebuggerException("failed to get stack frames") + stackframes = trace_response["body"]["stackFrames"] + path = stackframes[0]["source"]["path"] + addr = stackframes[0]["instructionPointerReference"] + if any( + self._debugger_state.bp_addr_map.get(self.dex_id_to_dap_id[dex_bp_id]) + == addr + for dex_bp_id in self.file_to_bp.get(path, []) + ): + # Step again now to get to the breakpoint. + step_req_id = self.send_message( + self.make_request( + "stepIn", {"threadId": self._debugger_state.thread} + ) + ) + response = self._await_response(step_req_id) + if not response["success"]: + raise DebuggerException("failed to step") + + def _get_launch_params(self, cmdline): + if self.context.options.target_run_args: + cmdline += shlex.split(self.context.options.target_run_args) + cwd = os.getcwd() + return { + "cwd": cwd, + "args": cmdline, + "program": self.context.options.executable, + "stopOnEntry": True, + } + + @staticmethod + def _evaluate_result_value( + expression: str, result_string: str, type_string: str | None + ) -> ValueIR: + could_evaluate = not any( + s in result_string + for s in [ + "Can't run the expression locally", + "use of undeclared identifier", + "no member named", + "Couldn't lookup symbols", + "Couldn't look up symbols", + "reference to local variable", + "invalid use of 'this' outside of a non-static member function", + ] + ) + + is_optimized_away = any( + s in result_string + for s in [ + "value may have been optimized out", + ] + ) + + is_irretrievable = any( + s in result_string + for s in [ + "couldn't get the value of variable", + "couldn't read its memory", + "couldn't read from memory", + "Cannot access memory at address", + "invalid address (fault address:", + ] + ) + + if could_evaluate and not is_irretrievable and not is_optimized_away: + error_string = None + else: + error_string = result_string + + return ValueIR( + expression=expression, + value=result_string, + type_name=type_string, + error_string=error_string, + could_evaluate=could_evaluate, + is_optimized_away=is_optimized_away, + is_irretrievable=is_irretrievable, + ) + + def _update_requested_bp_list( + self, bp_list: list[DAP.BreakpointRequest] + ) -> list[DAP.BreakpointRequest]: + """ "As lldb-dap cannot have multiple breakpoints at the same location with different conditions, we must + manually merge conditions here.""" + line_to_cond: dict[int, str | None] = {} + for bp in bp_list: + if bp.condition is None: + line_to_cond[bp.line] = None + continue + # If we have a condition, we merge it with the existing condition if one exists, unless the known condition + # is None in which case we preserve the None condition (as the underlying breakpoint should always be hit). + if bp.line not in line_to_cond: + line_to_cond[bp.line] = f"({bp.condition})" + elif line_to_cond[bp.line] is not None: + line_to_cond[bp.line] = f"{line_to_cond[bp.line]} || ({bp.condition})" + bp.condition = line_to_cond[bp.line] + return bp_list + + def _confirm_triggered_breakpoint_ids(self, dex_bp_ids): + """ "As lldb returns every breakpoint at the current PC regardless of whether their condition was met, we must + manually check conditions here.""" + confirmed_breakpoint_ids = set() + for dex_bp_id in dex_bp_ids: + _, _, cond = self.bp_info[dex_bp_id] + if cond is None: + confirmed_breakpoint_ids.add(dex_bp_id) + continue + valueIR = self.evaluate_expression(cond) + if valueIR.type_name == "bool" and valueIR.value == "true": + confirmed_breakpoint_ids.add(dex_bp_id) + return confirmed_breakpoint_ids |
