diff options
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py')
-rw-r--r-- | debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py new file mode 100644 index 00000000000..425d3c2adb1 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py @@ -0,0 +1,244 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Interface for communicating with the LLDB debugger via its python interface. +""" + +import imp +import os +from subprocess import CalledProcessError, check_output, STDOUT +import sys + +from dex.debugger.DebuggerBase import DebuggerBase +from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR +from dex.dextIR import StackFrame, SourceLocation, ProgramState +from dex.utils.Exceptions import DebuggerException, LoadDebuggerException +from dex.utils.ReturnCode import ReturnCode + + +class LLDB(DebuggerBase): + def __init__(self, context, *args): + self.lldb_executable = context.options.lldb_executable + self._debugger = None + self._target = None + self._process = None + self._thread = None + super(LLDB, self).__init__(context, *args) + + def _custom_init(self): + self._debugger = self._interface.SBDebugger.Create() + self._debugger.SetAsync(False) + self._target = self._debugger.CreateTargetWithFileAndArch( + self.context.options.executable, self.context.options.arch) + if not self._target: + raise LoadDebuggerException( + 'could not create target for executable "{}" with arch:{}'. + format(self.context.options.executable, + self.context.options.arch)) + + def _custom_exit(self): + if getattr(self, '_process', None): + self._process.Kill() + if getattr(self, '_debugger', None) and getattr(self, '_target', None): + self._debugger.DeleteTarget(self._target) + + def _translate_stop_reason(self, reason): + if reason == self._interface.eStopReasonNone: + return None + if reason == self._interface.eStopReasonBreakpoint: + return StopReason.BREAKPOINT + if reason == self._interface.eStopReasonPlanComplete: + return StopReason.STEP + if reason == self._interface.eStopReasonThreadExiting: + return StopReason.PROGRAM_EXIT + if reason == self._interface.eStopReasonException: + return StopReason.ERROR + return StopReason.OTHER + + def _load_interface(self): + try: + args = [self.lldb_executable, '-P'] + pythonpath = check_output( + args, stderr=STDOUT).rstrip().decode('utf-8') + except CalledProcessError as e: + raise LoadDebuggerException(str(e), sys.exc_info()) + except OSError as e: + raise LoadDebuggerException( + '{} ["{}"]'.format(e.strerror, self.lldb_executable), + sys.exc_info()) + + if not os.path.isdir(pythonpath): + raise LoadDebuggerException( + 'path "{}" does not exist [result of {}]'.format( + pythonpath, args), sys.exc_info()) + + try: + module_info = imp.find_module('lldb', [pythonpath]) + return imp.load_module('lldb', *module_info) + except ImportError as e: + msg = str(e) + if msg.endswith('not a valid Win32 application.'): + msg = '{} [Are you mixing 32-bit and 64-bit binaries?]'.format( + msg) + raise LoadDebuggerException(msg, sys.exc_info()) + + @classmethod + def get_name(cls): + return 'lldb' + + @classmethod + def get_option_name(cls): + return 'lldb' + + @property + def version(self): + try: + return self._interface.SBDebugger_GetVersionString() + except AttributeError: + return None + + def clear_breakpoints(self): + self._target.DeleteAllBreakpoints() + + def add_breakpoint(self, file_, line): + if not self._target.BreakpointCreateByLocation(file_, line): + raise LoadDebuggerException( + 'could not add breakpoint [{}:{}]'.format(file_, line)) + + def launch(self): + self._process = self._target.LaunchSimple(None, None, os.getcwd()) + if not self._process or self._process.GetNumThreads() == 0: + raise DebuggerException('could not launch process') + if self._process.GetNumThreads() != 1: + raise DebuggerException('multiple threads not supported') + self._thread = self._process.GetThreadAtIndex(0) + assert self._thread, (self._process, self._thread) + + def step(self): + self._thread.StepInto() + + def go(self) -> ReturnCode: + self._process.Continue() + return ReturnCode.OK + + def get_step_info(self): + frames = [] + state_frames = [] + + for i in range(0, self._thread.GetNumFrames()): + sb_frame = self._thread.GetFrameAtIndex(i) + sb_line = sb_frame.GetLineEntry() + sb_filespec = sb_line.GetFileSpec() + + try: + path = os.path.join(sb_filespec.GetDirectory(), + sb_filespec.GetFilename()) + except (AttributeError, TypeError): + path = None + + function = self._sanitize_function_name(sb_frame.GetFunctionName()) + + loc_dict = { + 'path': path, + 'lineno': sb_line.GetLine(), + 'column': sb_line.GetColumn() + } + loc = LocIR(**loc_dict) + + frame = FrameIR( + function=function, is_inlined=sb_frame.IsInlined(), loc=loc) + + if any( + name in (frame.function or '') # pylint: disable=no-member + for name in self.frames_below_main): + break + + frames.append(frame) + + state_frame = StackFrame(function=frame.function, + is_inlined=frame.is_inlined, + location=SourceLocation(**loc_dict), + watches={}) + for expr in map( + lambda watch, idx=i: self.evaluate_expression(watch, idx), + self.watches): + state_frame.watches[expr.expression] = expr + state_frames.append(state_frame) + + if len(frames) == 1 and frames[0].function is None: + frames = [] + state_frames = [] + + reason = self._translate_stop_reason(self._thread.GetStopReason()) + + return StepIR( + step_index=self.step_index, frames=frames, stop_reason=reason, + program_state=ProgramState(state_frames)) + + @property + def is_running(self): + # We're not running in async mode so this is always False. + return False + + @property + def is_finished(self): + return not self._thread.GetFrameAtIndex(0) + + @property + def frames_below_main(self): + return ['__scrt_common_main_seh', '__libc_start_main'] + + def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: + result = self._thread.GetFrameAtIndex(frame_idx + ).EvaluateExpression(expression) + error_string = str(result.error) + + value = result.value + could_evaluate = not any(s in error_string for s in [ + "Can't run the expression locally", + "use of undeclared identifier", + "no member named", + "Couldn't lookup symbols", + "reference to local variable", + "invalid use of 'this' outside of a non-static member function", + ]) + + is_optimized_away = any(s in error_string for s in [ + 'value may have been optimized out', + ]) + + is_irretrievable = any(s in error_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: + assert error_string == 'success', (error_string, expression, value) + # assert result.value is not None, (result.value, expression) + + if error_string == 'success': + error_string = None + + # attempt to find expression as a variable, if found, take the variable + # obj's type information as it's 'usually' more accurate. + var_result = self._thread.GetFrameAtIndex(frame_idx).FindVariable(expression) + if str(var_result.error) == 'success': + type_name = var_result.type.GetDisplayTypeName() + else: + type_name = result.type.GetDisplayTypeName() + + return ValueIR( + expression=expression, + value=value, + type_name=type_name, + error_string=error_string, + could_evaluate=could_evaluate, + is_optimized_away=is_optimized_away, + is_irretrievable=is_irretrievable, + ) |