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