1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7"""Interface for communicating with the LLDB debugger via its python interface. 8""" 9 10import imp 11import os 12from subprocess import CalledProcessError, check_output, STDOUT 13import sys 14 15from dex.debugger.DebuggerBase import DebuggerBase 16from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR 17from dex.dextIR import StackFrame, SourceLocation, ProgramState 18from dex.utils.Exceptions import DebuggerException, LoadDebuggerException 19from dex.utils.ReturnCode import ReturnCode 20 21 22class LLDB(DebuggerBase): 23 def __init__(self, context, *args): 24 self.lldb_executable = context.options.lldb_executable 25 self._debugger = None 26 self._target = None 27 self._process = None 28 self._thread = None 29 # Map {id (int): condition (str)} for breakpoints which have a 30 # condition. See get_triggered_breakpoint_ids usage for more info. 31 self._breakpoint_conditions = {} 32 super(LLDB, self).__init__(context, *args) 33 34 def _custom_init(self): 35 self._debugger = self._interface.SBDebugger.Create() 36 self._debugger.SetAsync(False) 37 self._target = self._debugger.CreateTargetWithFileAndArch( 38 self.context.options.executable, self.context.options.arch) 39 if not self._target: 40 raise LoadDebuggerException( 41 'could not create target for executable "{}" with arch:{}'. 42 format(self.context.options.executable, 43 self.context.options.arch)) 44 45 def _custom_exit(self): 46 if getattr(self, '_process', None): 47 self._process.Kill() 48 if getattr(self, '_debugger', None) and getattr(self, '_target', None): 49 self._debugger.DeleteTarget(self._target) 50 51 def _translate_stop_reason(self, reason): 52 if reason == self._interface.eStopReasonNone: 53 return None 54 if reason == self._interface.eStopReasonBreakpoint: 55 return StopReason.BREAKPOINT 56 if reason == self._interface.eStopReasonPlanComplete: 57 return StopReason.STEP 58 if reason == self._interface.eStopReasonThreadExiting: 59 return StopReason.PROGRAM_EXIT 60 if reason == self._interface.eStopReasonException: 61 return StopReason.ERROR 62 return StopReason.OTHER 63 64 def _load_interface(self): 65 try: 66 args = [self.lldb_executable, '-P'] 67 pythonpath = check_output( 68 args, stderr=STDOUT).rstrip().decode('utf-8') 69 except CalledProcessError as e: 70 raise LoadDebuggerException(str(e), sys.exc_info()) 71 except OSError as e: 72 raise LoadDebuggerException( 73 '{} ["{}"]'.format(e.strerror, self.lldb_executable), 74 sys.exc_info()) 75 76 if not os.path.isdir(pythonpath): 77 raise LoadDebuggerException( 78 'path "{}" does not exist [result of {}]'.format( 79 pythonpath, args), sys.exc_info()) 80 81 try: 82 module_info = imp.find_module('lldb', [pythonpath]) 83 return imp.load_module('lldb', *module_info) 84 except ImportError as e: 85 msg = str(e) 86 if msg.endswith('not a valid Win32 application.'): 87 msg = '{} [Are you mixing 32-bit and 64-bit binaries?]'.format( 88 msg) 89 raise LoadDebuggerException(msg, sys.exc_info()) 90 91 @classmethod 92 def get_name(cls): 93 return 'lldb' 94 95 @classmethod 96 def get_option_name(cls): 97 return 'lldb' 98 99 @property 100 def version(self): 101 try: 102 return self._interface.SBDebugger_GetVersionString() 103 except AttributeError: 104 return None 105 106 def clear_breakpoints(self): 107 self._target.DeleteAllBreakpoints() 108 109 def _add_breakpoint(self, file_, line): 110 return self._add_conditional_breakpoint(file_, line, None) 111 112 def _add_conditional_breakpoint(self, file_, line, condition): 113 bp = self._target.BreakpointCreateByLocation(file_, line) 114 if not bp: 115 raise DebuggerException( 116 'could not add breakpoint [{}:{}]'.format(file_, line)) 117 id = bp.GetID() 118 if condition: 119 bp.SetCondition(condition) 120 assert id not in self._breakpoint_conditions 121 self._breakpoint_conditions[id] = condition 122 return id 123 124 def _evaulate_breakpoint_condition(self, id): 125 """Evaluate the breakpoint condition and return the result. 126 127 Returns True if a conditional breakpoint with the specified id cannot 128 be found (i.e. assume it is an unconditional breakpoint). 129 """ 130 try: 131 condition = self._breakpoint_conditions[id] 132 except KeyError: 133 # This must be an unconditional breakpoint. 134 return True 135 valueIR = self.evaluate_expression(condition) 136 return valueIR.type_name == 'bool' and valueIR.value == 'true' 137 138 def get_triggered_breakpoint_ids(self): 139 # Breakpoints can only have been triggered if we've hit one. 140 stop_reason = self._translate_stop_reason(self._thread.GetStopReason()) 141 if stop_reason != StopReason.BREAKPOINT: 142 return [] 143 breakpoint_ids = set() 144 # When the stop reason is eStopReasonBreakpoint, GetStopReasonDataCount 145 # counts all breakpoints associated with the location that lldb has 146 # stopped at, regardless of their condition. I.e. Even if we have two 147 # breakpoints at the same source location that have mutually exclusive 148 # conditions, both will be counted by GetStopReasonDataCount when 149 # either condition is true. Check each breakpoint condition manually to 150 # filter the list down to breakpoints that have caused this stop. 151 # 152 # Breakpoints have two data parts: Breakpoint ID, Location ID. We're 153 # only interested in the Breakpoint ID so we skip every other item. 154 for i in range(0, self._thread.GetStopReasonDataCount(), 2): 155 id = self._thread.GetStopReasonDataAtIndex(i) 156 if self._evaulate_breakpoint_condition(id): 157 breakpoint_ids.add(id) 158 return breakpoint_ids 159 160 def delete_breakpoint(self, id): 161 bp = self._target.FindBreakpointByID(id) 162 if not bp: 163 # The ID is not valid. 164 raise KeyError 165 try: 166 del self._breakpoint_conditions[id] 167 except KeyError: 168 # This must be an unconditional breakpoint. 169 pass 170 self._target.BreakpointDelete(id) 171 172 def launch(self): 173 self._process = self._target.LaunchSimple(None, None, os.getcwd()) 174 if not self._process or self._process.GetNumThreads() == 0: 175 raise DebuggerException('could not launch process') 176 if self._process.GetNumThreads() != 1: 177 raise DebuggerException('multiple threads not supported') 178 self._thread = self._process.GetThreadAtIndex(0) 179 assert self._thread, (self._process, self._thread) 180 181 def step(self): 182 self._thread.StepInto() 183 184 def go(self) -> ReturnCode: 185 self._process.Continue() 186 return ReturnCode.OK 187 188 def _get_step_info(self, watches, step_index): 189 frames = [] 190 state_frames = [] 191 192 for i in range(0, self._thread.GetNumFrames()): 193 sb_frame = self._thread.GetFrameAtIndex(i) 194 sb_line = sb_frame.GetLineEntry() 195 sb_filespec = sb_line.GetFileSpec() 196 197 try: 198 path = os.path.join(sb_filespec.GetDirectory(), 199 sb_filespec.GetFilename()) 200 except (AttributeError, TypeError): 201 path = None 202 203 function = self._sanitize_function_name(sb_frame.GetFunctionName()) 204 205 loc_dict = { 206 'path': path, 207 'lineno': sb_line.GetLine(), 208 'column': sb_line.GetColumn() 209 } 210 loc = LocIR(**loc_dict) 211 212 frame = FrameIR( 213 function=function, is_inlined=sb_frame.IsInlined(), loc=loc) 214 215 if any( 216 name in (frame.function or '') # pylint: disable=no-member 217 for name in self.frames_below_main): 218 break 219 220 frames.append(frame) 221 222 state_frame = StackFrame(function=frame.function, 223 is_inlined=frame.is_inlined, 224 location=SourceLocation(**loc_dict), 225 watches={}) 226 for expr in map( 227 lambda watch, idx=i: self.evaluate_expression(watch, idx), 228 watches): 229 state_frame.watches[expr.expression] = expr 230 state_frames.append(state_frame) 231 232 if len(frames) == 1 and frames[0].function is None: 233 frames = [] 234 state_frames = [] 235 236 reason = self._translate_stop_reason(self._thread.GetStopReason()) 237 238 return StepIR( 239 step_index=step_index, frames=frames, stop_reason=reason, 240 program_state=ProgramState(state_frames)) 241 242 @property 243 def is_running(self): 244 # We're not running in async mode so this is always False. 245 return False 246 247 @property 248 def is_finished(self): 249 return not self._thread.GetFrameAtIndex(0) 250 251 @property 252 def frames_below_main(self): 253 return ['__scrt_common_main_seh', '__libc_start_main'] 254 255 def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: 256 result = self._thread.GetFrameAtIndex(frame_idx 257 ).EvaluateExpression(expression) 258 error_string = str(result.error) 259 260 value = result.value 261 could_evaluate = not any(s in error_string for s in [ 262 "Can't run the expression locally", 263 "use of undeclared identifier", 264 "no member named", 265 "Couldn't lookup symbols", 266 "reference to local variable", 267 "invalid use of 'this' outside of a non-static member function", 268 ]) 269 270 is_optimized_away = any(s in error_string for s in [ 271 'value may have been optimized out', 272 ]) 273 274 is_irretrievable = any(s in error_string for s in [ 275 "couldn't get the value of variable", 276 "couldn't read its memory", 277 "couldn't read from memory", 278 "Cannot access memory at address", 279 "invalid address (fault address:", 280 ]) 281 282 if could_evaluate and not is_irretrievable and not is_optimized_away: 283 assert error_string == 'success', (error_string, expression, value) 284 # assert result.value is not None, (result.value, expression) 285 286 if error_string == 'success': 287 error_string = None 288 289 # attempt to find expression as a variable, if found, take the variable 290 # obj's type information as it's 'usually' more accurate. 291 var_result = self._thread.GetFrameAtIndex(frame_idx).FindVariable(expression) 292 if str(var_result.error) == 'success': 293 type_name = var_result.type.GetDisplayTypeName() 294 else: 295 type_name = result.type.GetDisplayTypeName() 296 297 return ValueIR( 298 expression=expression, 299 value=value, 300 type_name=type_name, 301 error_string=error_string, 302 could_evaluate=could_evaluate, 303 is_optimized_away=is_optimized_away, 304 is_irretrievable=is_irretrievable, 305 ) 306