1 2# 3# This file defines the layer that talks to lldb 4# 5 6import os, re, sys 7import lldb 8import vim 9from vim_ui import UI 10 11# ================================================= 12# Convert some enum value to its string counterpart 13# ================================================= 14 15# Shamelessly copy/pasted from lldbutil.py in the test suite 16def state_type_to_str(enum): 17 """Returns the stateType string given an enum.""" 18 if enum == lldb.eStateInvalid: 19 return "invalid" 20 elif enum == lldb.eStateUnloaded: 21 return "unloaded" 22 elif enum == lldb.eStateConnected: 23 return "connected" 24 elif enum == lldb.eStateAttaching: 25 return "attaching" 26 elif enum == lldb.eStateLaunching: 27 return "launching" 28 elif enum == lldb.eStateStopped: 29 return "stopped" 30 elif enum == lldb.eStateRunning: 31 return "running" 32 elif enum == lldb.eStateStepping: 33 return "stepping" 34 elif enum == lldb.eStateCrashed: 35 return "crashed" 36 elif enum == lldb.eStateDetached: 37 return "detached" 38 elif enum == lldb.eStateExited: 39 return "exited" 40 elif enum == lldb.eStateSuspended: 41 return "suspended" 42 else: 43 raise Exception("Unknown StateType enum") 44 45class StepType: 46 INSTRUCTION = 1 47 INSTRUCTION_OVER = 2 48 INTO = 3 49 OVER = 4 50 OUT = 5 51 52class LLDBController(object): 53 """ Handles Vim and LLDB events such as commands and lldb events. """ 54 55 # Timeouts (sec) for waiting on new events. Because vim is not multi-threaded, we are restricted to 56 # servicing LLDB events from the main UI thread. Usually, we only process events that are already 57 # sitting on the queue. But in some situations (when we are expecting an event as a result of some 58 # user interaction) we want to wait for it. The constants below set these wait period in which the 59 # Vim UI is "blocked". Lower numbers will make Vim more responsive, but LLDB will be delayed and higher 60 # numbers will mean that LLDB events are processed faster, but the Vim UI may appear less responsive at 61 # times. 62 eventDelayStep = 2 63 eventDelayLaunch = 1 64 eventDelayContinue = 1 65 66 def __init__(self): 67 """ Creates the LLDB SBDebugger object and initializes the UI class. """ 68 self.target = None 69 self.process = None 70 self.load_dependent_modules = True 71 72 self.dbg = lldb.SBDebugger.Create() 73 self.commandInterpreter = self.dbg.GetCommandInterpreter() 74 75 self.ui = UI() 76 77 def completeCommand(self, a, l, p): 78 """ Returns a list of viable completions for command a with length l and cursor at p """ 79 80 assert l[0] == 'L' 81 # Remove first 'L' character that all commands start with 82 l = l[1:] 83 84 # Adjust length as string has 1 less character 85 p = int(p) - 1 86 87 result = lldb.SBStringList() 88 num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result) 89 90 if num == -1: 91 # FIXME: insert completion character... what's a completion character? 92 pass 93 elif num == -2: 94 # FIXME: replace line with result.GetStringAtIndex(0) 95 pass 96 97 if result.GetSize() > 0: 98 results = filter(None, [result.GetStringAtIndex(x) for x in range(result.GetSize())]) 99 return results 100 else: 101 return [] 102 103 def doStep(self, stepType): 104 """ Perform a step command and block the UI for eventDelayStep seconds in order to process 105 events on lldb's event queue. 106 FIXME: if the step does not complete in eventDelayStep seconds, we relinquish control to 107 the main thread to avoid the appearance of a "hang". If this happens, the UI will 108 update whenever; usually when the user moves the cursor. This is somewhat annoying. 109 """ 110 if not self.process: 111 sys.stderr.write("No process to step") 112 return 113 114 t = self.process.GetSelectedThread() 115 if stepType == StepType.INSTRUCTION: 116 t.StepInstruction(False) 117 if stepType == StepType.INSTRUCTION_OVER: 118 t.StepInstruction(True) 119 elif stepType == StepType.INTO: 120 t.StepInto() 121 elif stepType == StepType.OVER: 122 t.StepOver() 123 elif stepType == StepType.OUT: 124 t.StepOut() 125 126 self.processPendingEvents(self.eventDelayStep, True) 127 128 def doSelect(self, command, args): 129 """ Like doCommand, but suppress output when "select" is the first argument.""" 130 a = args.split(' ') 131 return self.doCommand(command, args, "select" != a[0], True) 132 133 def doProcess(self, args): 134 """ Handle 'process' command. If 'launch' is requested, use doLaunch() instead 135 of the command interpreter to start the inferior process. 136 """ 137 a = args.split(' ') 138 if len(args) == 0 or (len(a) > 0 and a[0] != 'launch'): 139 self.doCommand("process", args) 140 #self.ui.update(self.target, "", self) 141 else: 142 self.doLaunch('-s' not in args, "") 143 144 def doAttach(self, process_name): 145 """ Handle process attach. """ 146 error = lldb.SBError() 147 148 self.processListener = lldb.SBListener("process_event_listener") 149 self.target = self.dbg.CreateTarget('') 150 self.process = self.target.AttachToProcessWithName(self.processListener, process_name, False, error) 151 if not error.Success(): 152 sys.stderr.write("Error during attach: " + str(error)) 153 return 154 155 self.ui.activate() 156 self.pid = self.process.GetProcessID() 157 158 print "Attached to %s (pid=%d)" % (process_name, self.pid) 159 160 def doDetach(self): 161 if self.process is not None and self.process.IsValid(): 162 pid = self.process.GetProcessID() 163 state = state_type_to_str(self.process.GetState()) 164 self.process.Detach() 165 self.processPendingEvents(self.eventDelayLaunch) 166 167 def doLaunch(self, stop_at_entry, args): 168 """ Handle process launch. """ 169 error = lldb.SBError() 170 171 fs = self.target.GetExecutable() 172 exe = os.path.join(fs.GetDirectory(), fs.GetFilename()) 173 if self.process is not None and self.process.IsValid(): 174 pid = self.process.GetProcessID() 175 state = state_type_to_str(self.process.GetState()) 176 self.process.Destroy() 177 178 launchInfo = lldb.SBLaunchInfo(args.split(' ')) 179 self.process = self.target.Launch(launchInfo, error) 180 if not error.Success(): 181 sys.stderr.write("Error during launch: " + str(error)) 182 return 183 184 # launch succeeded, store pid and add some event listeners 185 self.pid = self.process.GetProcessID() 186 self.processListener = lldb.SBListener("process_event_listener") 187 self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged) 188 189 print "Launched %s %s (pid=%d)" % (exe, args, self.pid) 190 191 if not stop_at_entry: 192 self.doContinue() 193 else: 194 self.processPendingEvents(self.eventDelayLaunch) 195 196 def doTarget(self, args): 197 """ Pass target command to interpreter, except if argument is not one of the valid options, or 198 is create, in which case try to create a target with the argument as the executable. For example: 199 target list ==> handled by interpreter 200 target create blah ==> custom creation of target 'blah' 201 target blah ==> also creates target blah 202 """ 203 target_args = [#"create", 204 "delete", 205 "list", 206 "modules", 207 "select", 208 "stop-hook", 209 "symbols", 210 "variable"] 211 212 a = args.split(' ') 213 if len(args) == 0 or (len(a) > 0 and a[0] in target_args): 214 self.doCommand("target", args) 215 return 216 elif len(a) > 1 and a[0] == "create": 217 exe = a[1] 218 elif len(a) == 1 and a[0] not in target_args: 219 exe = a[0] 220 221 err = lldb.SBError() 222 self.target = self.dbg.CreateTarget(exe, None, None, self.load_dependent_modules, err) 223 if not self.target: 224 sys.stderr.write("Error creating target %s. %s" % (str(exe), str(err))) 225 return 226 227 self.ui.activate() 228 self.ui.update(self.target, "created target %s" % str(exe), self) 229 230 def doContinue(self): 231 """ Handle 'contiue' command. 232 FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param. 233 """ 234 if not self.process or not self.process.IsValid(): 235 sys.stderr.write("No process to continue") 236 return 237 238 self.process.Continue() 239 self.processPendingEvents(self.eventDelayContinue) 240 241 def doBreakpoint(self, args): 242 """ Handle breakpoint command with command interpreter, except if the user calls 243 "breakpoint" with no other args, in which case add a breakpoint at the line 244 under the cursor. 245 """ 246 a = args.split(' ') 247 if len(args) == 0: 248 show_output = False 249 250 # User called us with no args, so toggle the bp under cursor 251 cw = vim.current.window 252 cb = vim.current.buffer 253 name = cb.name 254 line = cw.cursor[0] 255 256 # Since the UI is responsbile for placing signs at bp locations, we have to 257 # ask it if there already is one or more breakpoints at (file, line)... 258 if self.ui.haveBreakpoint(name, line): 259 bps = self.ui.getBreakpoints(name, line) 260 args = "delete %s" % " ".join([str(b.GetID()) for b in bps]) 261 self.ui.deleteBreakpoints(name, line) 262 else: 263 args = "set -f %s -l %d" % (name, line) 264 else: 265 show_output = True 266 267 self.doCommand("breakpoint", args, show_output) 268 return 269 270 def doRefresh(self): 271 """ process pending events and update UI on request """ 272 status = self.processPendingEvents() 273 274 def doShow(self, name): 275 """ handle :Lshow <name> """ 276 if not name: 277 self.ui.activate() 278 return 279 280 if self.ui.showWindow(name): 281 self.ui.update(self.target, "", self) 282 283 def doHide(self, name): 284 """ handle :Lhide <name> """ 285 if self.ui.hideWindow(name): 286 self.ui.update(self.target, "", self) 287 288 def doExit(self): 289 self.dbg.Terminate() 290 self.dbg = None 291 292 def getCommandResult(self, command, command_args): 293 """ Run cmd in the command interpreter and returns (success, output) """ 294 result = lldb.SBCommandReturnObject() 295 cmd = "%s %s" % (command, command_args) 296 297 self.commandInterpreter.HandleCommand(cmd, result) 298 return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError()) 299 300 def doCommand(self, command, command_args, print_on_success = True, goto_file=False): 301 """ Run cmd in interpreter and print result (success or failure) on the vim status line. """ 302 (success, output) = self.getCommandResult(command, command_args) 303 if success: 304 self.ui.update(self.target, "", self, goto_file) 305 if len(output) > 0 and print_on_success: 306 print output 307 else: 308 sys.stderr.write(output) 309 310 def getCommandOutput(self, command, command_args=""): 311 """ runs cmd in the command interpreter andreturns (status, result) """ 312 result = lldb.SBCommandReturnObject() 313 cmd = "%s %s" % (command, command_args) 314 self.commandInterpreter.HandleCommand(cmd, result) 315 return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError()) 316 317 def processPendingEvents(self, wait_seconds=0, goto_file=True): 318 """ Handle any events that are queued from the inferior. 319 Blocks for at most wait_seconds, or if wait_seconds == 0, 320 process only events that are already queued. 321 """ 322 323 status = None 324 num_events_handled = 0 325 326 if self.process is not None: 327 event = lldb.SBEvent() 328 old_state = self.process.GetState() 329 new_state = None 330 done = False 331 if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited: 332 # Early-exit if we are in 'boring' states 333 pass 334 else: 335 while not done and self.processListener is not None: 336 if not self.processListener.PeekAtNextEvent(event): 337 if wait_seconds > 0: 338 # No events on the queue, but we are allowed to wait for wait_seconds 339 # for any events to show up. 340 self.processListener.WaitForEvent(wait_seconds, event) 341 new_state = lldb.SBProcess.GetStateFromEvent(event) 342 343 num_events_handled += 1 344 345 done = not self.processListener.PeekAtNextEvent(event) 346 else: 347 # An event is on the queue, process it here. 348 self.processListener.GetNextEvent(event) 349 new_state = lldb.SBProcess.GetStateFromEvent(event) 350 351 # continue if stopped after attaching 352 if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped: 353 self.process.Continue() 354 355 # If needed, perform any event-specific behaviour here 356 num_events_handled += 1 357 358 if num_events_handled == 0: 359 pass 360 else: 361 if old_state == new_state: 362 status = "" 363 self.ui.update(self.target, status, self, goto_file) 364 365 366def returnCompleteCommand(a, l, p): 367 """ Returns a "\n"-separated string with possible completion results 368 for command a with length l and cursor at p. 369 """ 370 separator = "\n" 371 results = ctrl.completeCommand(a, l, p) 372 vim.command('return "%s%s"' % (separator.join(results), separator)) 373 374def returnCompleteWindow(a, l, p): 375 """ Returns a "\n"-separated string with possible completion results 376 for commands that expect a window name parameter (like hide/show). 377 FIXME: connect to ctrl.ui instead of hardcoding the list here 378 """ 379 separator = "\n" 380 results = ['breakpoints', 'backtrace', 'disassembly', 'locals', 'threads', 'registers'] 381 vim.command('return "%s%s"' % (separator.join(results), separator)) 382 383global ctrl 384ctrl = LLDBController() 385