Example #1
0
    def __init__(self):
        """ Creates the LLDB SBDebugger object and initializes the UI class. """
        self.target = None
        self.process = None
        self.load_dependent_modules = True

        self.dbg = lldb.SBDebugger.Create()
        self.commandInterpreter = self.dbg.GetCommandInterpreter()

        self.ui = UI()
Example #2
0
  def __init__(self):
    """ Creates the LLDB SBDebugger object and initializes the UI class. """
    self.target = None
    self.process = None
    self.load_dependent_modules = True

    self.dbg = lldb.SBDebugger.Create()
    self.commandInterpreter = self.dbg.GetCommandInterpreter()
    self.commandInterpreter.HandleCommand("settings set target.load-script-from-symbol-file false", lldb.SBCommandReturnObject())

    self.ui = UI()
Example #3
0
    def __init__(self):
        """ Creates the LLDB SBDebugger object and initializes the UI class. """
        self.target = None
        self.process = None
        self.load_dependent_modules = True

        self.dbg = lldb.SBDebugger.Create()
        # during step/continue do not return from function until process stops
        # async is enabled by default, but overridden in vimrc g:lldb_enable_async
        vimrc_lldb_async = vim.eval('s:lldb_async')
        if (vimrc_lldb_async == 0):
            self.dbg.SetAsync(False)
        else:
            self.dbg.SetAsync(True)

        self.commandInterpreter = self.dbg.GetCommandInterpreter()

        self.ui = UI()
Example #4
0
    def __init__(self):
        """ Creates the LLDB SBDebugger object and initializes the UI class. """
        self.target = None
        self.process = None
        self.load_dependent_modules = True

        self.dbg = lldb.SBDebugger.Create()
        self.commandInterpreter = self.dbg.GetCommandInterpreter()

        self.ui = UI()
Example #5
0
  def __init__(self):
    """ Creates the LLDB SBDebugger object and initializes the UI class. """
    self.target = None
    self.process = None
    self.load_dependent_modules = True

    self.dbg = lldb.SBDebugger.Create()
    self.commandInterpreter = self.dbg.GetCommandInterpreter()
    self.commandInterpreter.HandleCommand("settings set target.load-script-from-symbol-file false", lldb.SBCommandReturnObject())

    self.ui = UI()
Example #6
0
  def __init__(self, vifx):
    """ Creates the LLDB SBDebugger object and initializes the UI class.
        vifx represents the parent LLInterface object.
    """
    self.target = None
    self.process = None
    self.load_dependent_modules = True

    self.dbg = lldb.SBDebugger.Create()
    self.command_interpreter = self.dbg.GetCommandInterpreter()

    self.vifx = vifx
    self.ui = UI(vifx)
Example #7
0
class LLDBController(object):
  """ Handles Vim and LLDB events such as commands and lldb events. """

  # Timeouts (sec) for waiting on new events. Because vim is not multi-threaded, we are restricted to
  # servicing LLDB events from the main UI thread. Usually, we only process events that are already
  # sitting on the queue. But in some situations (when we are expecting an event as a result of some
  # user interaction) we want to wait for it. The constants below set these wait period in which the
  # Vim UI is "blocked". Lower numbers will make Vim more responsive, but LLDB will be delayed and higher
  # numbers will mean that LLDB events are processed faster, but the Vim UI may appear less responsive at
  # times.
  eventDelayStep = 2
  eventDelayLaunch = 1
  eventDelayContinue = 1

  def __init__(self):
    """ Creates the LLDB SBDebugger object and initializes the UI class. """
    self.target = None
    self.process = None
    self.load_dependent_modules = True

    self.dbg = lldb.SBDebugger.Create()
    self.commandInterpreter = self.dbg.GetCommandInterpreter()
    self.commandInterpreter.HandleCommand("settings set target.load-script-from-symbol-file false", lldb.SBCommandReturnObject())

    self.ui = UI()

  def completeCommand(self, a, l, p):
    """ Returns a list of viable completions for command a with length l and cursor at p  """

    assert l[0] == 'L'
    # Remove first 'L' character that all commands start with
    l = l[1:]

    # Adjust length as string has 1 less character
    p = int(p) - 1

    result = lldb.SBStringList()
    num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result)

    if num == -1:
      # FIXME: insert completion character... what's a completion character?
      pass
    elif num == -2:
      # FIXME: replace line with result.GetStringAtIndex(0)
      pass

    if result.GetSize() > 0:
      results =  filter(None, [result.GetStringAtIndex(x) for x in range(result.GetSize())])
      return results
    else:
      return []

  def doStep(self, stepType):
    """ Perform a step command and block the UI for eventDelayStep seconds in order to process
        events on lldb's event queue.
        FIXME: if the step does not complete in eventDelayStep seconds, we relinquish control to
               the main thread to avoid the appearance of a "hang". If this happens, the UI will
               update whenever; usually when the user moves the cursor. This is somewhat annoying.
    """
    if not self.process:
      sys.stderr.write("No process to step")
      return
    
    t = self.process.GetSelectedThread()
    if stepType == StepType.INSTRUCTION:
      t.StepInstruction(False)
    if stepType == StepType.INSTRUCTION_OVER:
      t.StepInstruction(True)
    elif stepType == StepType.INTO:
      t.StepInto()
    elif stepType == StepType.OVER:
      t.StepOver()
    elif stepType == StepType.OUT:
      t.StepOut()

    self.processPendingEvents(self.eventDelayStep, True)

  def doSelect(self, command, args):
    """ Like doCommand, but suppress output when "select" is the first argument."""
    a = args.split(' ')
    return self.doCommand(command, args, "select" != a[0], True)

  def doProcess(self, args):
    """ Handle 'process' command. If 'launch' is requested, use doLaunch() instead
        of the command interpreter to start the inferior process.
    """
    a = args.split(' ')
    if len(args) == 0 or (len(a) > 0 and a[0] != 'launch'):
      self.doCommand("process", args)
      #self.ui.update(self.target, "", self)
    else:
      self.doLaunch('-s' not in args, "")
  
  def doAttachById(self, process_id):
    """ Handle process attach.  """
    error = lldb.SBError()
    
    self.processListener = lldb.SBListener("process_event_listener")
    self.target = self.dbg.CreateTarget('')
    self.process = self.target.AttachToProcessWithID(self.processListener, int(process_id), error)
    if not error.Success():
      sys.stderr.write("Error during attach: " + str(error))
      return

    self.ui.activate()
    self.pid = self.process.GetProcessID()

    print "Attached to %s (pid=%d)" % (process_id, self.pid)

  def doAttach(self, process_name):
    """ Handle process attach.  """
    error = lldb.SBError()
    
    self.processListener = lldb.SBListener("process_event_listener")
    self.target = self.dbg.CreateTarget('')
    self.process = self.target.AttachToProcessWithName(self.processListener, process_name, False, error)
    if not error.Success():
      sys.stderr.write("Error during attach: " + str(error))
      return

    self.ui.activate()
    self.pid = self.process.GetProcessID()

    print "Attached to %s (pid=%d)" % (process_name, self.pid)

  def doDetach(self):
    if self.process is not None and self.process.IsValid():
      pid = self.process.GetProcessID()
      state = state_type_to_str(self.process.GetState())
      self.process.Detach()
      self.processPendingEvents(self.eventDelayLaunch)

  def doLaunch(self, stop_at_entry, args):
    """ Handle process launch.  """
    error = lldb.SBError()

    fs = self.target.GetExecutable()
    exe = os.path.join(fs.GetDirectory(), fs.GetFilename())
    if self.process is not None and self.process.IsValid():
      pid = self.process.GetProcessID()
      state = state_type_to_str(self.process.GetState())
      self.process.Destroy()

    launchInfo = lldb.SBLaunchInfo(args.split(' '))
    self.process = self.target.Launch(launchInfo, error)
    if not error.Success():
      sys.stderr.write("Error during launch: " + str(error))
      return

    # launch succeeded, store pid and add some event listeners
    self.pid = self.process.GetProcessID()
    self.processListener = lldb.SBListener("process_event_listener")
    self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged)

    print "Launched %s %s (pid=%d)" % (exe, args, self.pid)

    if not stop_at_entry:
      self.doContinue()
    else:
      self.processPendingEvents(self.eventDelayLaunch)

  def doTarget(self, args):
    """ Pass target command to interpreter, except if argument is not one of the valid options, or
        is create, in which case try to create a target with the argument as the executable. For example:
          target list        ==> handled by interpreter
          target create blah ==> custom creation of target 'blah'
          target blah        ==> also creates target blah
    """
    target_args = [#"create",
                   "delete",
                   "list",
                   "modules",
                   "select",
                   "stop-hook",
                   "symbols",
                   "variable"]

    a = args.split(' ')
    if len(args) == 0 or (len(a) > 0 and a[0] in target_args):
      self.doCommand("target", args)
      return
    elif len(a) > 1 and a[0] == "create":
      exe = a[1]
    elif len(a) == 1 and a[0] not in target_args:
      exe = a[0]

    err = lldb.SBError()
    self.target = self.dbg.CreateTarget(exe, None, None, self.load_dependent_modules, err)
    if not self.target:
      sys.stderr.write("Error creating target %s. %s" % (str(exe), str(err)))
      return

    self.ui.activate()
    self.ui.update(self.target, "created target %s" % str(exe), self)

  def doContinue(self):
    """ Handle 'contiue' command.
        FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param.
    """
    if not self.process or not self.process.IsValid():
      sys.stderr.write("No process to continue")
      return

    self.process.Continue()
    self.processPendingEvents(self.eventDelayContinue)

  def doBreakpoint(self, args):
    """ Handle breakpoint command with command interpreter, except if the user calls
        "breakpoint" with no other args, in which case add a breakpoint at the line
        under the cursor.
    """
    a = args.split(' ')
    if len(args) == 0:
      show_output = False

      # User called us with no args, so toggle the bp under cursor
      cw = vim.current.window
      cb = vim.current.buffer
      name = cb.name
      line = cw.cursor[0]

      # Since the UI is responsbile for placing signs at bp locations, we have to
      # ask it if there already is one or more breakpoints at (file, line)...
      if self.ui.haveBreakpoint(name, line):
        bps = self.ui.getBreakpoints(name, line)
        args = "delete %s" % " ".join([str(b.GetID()) for b in bps])
        self.ui.deleteBreakpoints(name, line)
      else:
        args = "set -f %s -l %d" % (name, line)
    else:
      show_output = True

    self.doCommand("breakpoint", args, show_output)
    return

  def doRefresh(self):
    """ process pending events and update UI on request """
    status = self.processPendingEvents()

  def doShow(self, name):
    """ handle :Lshow <name> """
    if not name:
      self.ui.activate()
      return

    if self.ui.showWindow(name):
      self.ui.update(self.target, "", self)

  def doHide(self, name):
    """ handle :Lhide <name> """
    if self.ui.hideWindow(name):
      self.ui.update(self.target, "", self)

  def doExit(self):
    self.dbg.Terminate()
    self.dbg = None

  def getCommandResult(self, command, command_args):
    """ Run cmd in the command interpreter and returns (success, output) """
    result = lldb.SBCommandReturnObject()
    cmd = "%s %s" % (command, command_args)

    self.commandInterpreter.HandleCommand(cmd, result)
    return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError())

  def doCommand(self, command, command_args, print_on_success = True, goto_file=False):
    """ Run cmd in interpreter and print result (success or failure) on the vim status line. """
    (success, output) = self.getCommandResult(command, command_args)
    if success:
      self.ui.update(self.target, "", self, goto_file)
      if len(output) > 0 and print_on_success:
        print output
    else:
      sys.stderr.write(output)

  def getCommandOutput(self, command, command_args=""):
    """ runs cmd in the command interpreter andreturns (status, result) """
    result = lldb.SBCommandReturnObject()
    cmd = "%s %s" % (command, command_args)
    self.commandInterpreter.HandleCommand(cmd, result)
    return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError())

  def processPendingEvents(self, wait_seconds=0, goto_file=True):
    """ Handle any events that are queued from the inferior.
        Blocks for at most wait_seconds, or if wait_seconds == 0,
        process only events that are already queued.
    """

    status = None
    num_events_handled = 0

    if self.process is not None:
      event = lldb.SBEvent()
      old_state = self.process.GetState()
      new_state = None
      done = False
      if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited:
        # Early-exit if we are in 'boring' states
        pass
      else:
        while not done and self.processListener is not None:
          if not self.processListener.PeekAtNextEvent(event):
            if wait_seconds > 0:
              # No events on the queue, but we are allowed to wait for wait_seconds
              # for any events to show up.
              self.processListener.WaitForEvent(wait_seconds, event)
              new_state = lldb.SBProcess.GetStateFromEvent(event)

              num_events_handled += 1

            done = not self.processListener.PeekAtNextEvent(event)
          else:
            # An event is on the queue, process it here.
            self.processListener.GetNextEvent(event)
            new_state = lldb.SBProcess.GetStateFromEvent(event)

            # continue if stopped after attaching
            if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped:
              self.process.Continue()

            # If needed, perform any event-specific behaviour here
            num_events_handled += 1

    if num_events_handled == 0:
      pass
    else:
      if old_state == new_state:
        status = ""
      self.ui.update(self.target, status, self, goto_file)
Example #8
0
class LLDBController(object):
    """ Handles Vim and LLDB events such as commands and lldb events. """

    # Timeouts (sec) for waiting on new events. Because vim is not multi-threaded, we are restricted to
    # servicing LLDB events from the main UI thread. Usually, we only process events that are already
    # sitting on the queue. But in some situations (when we are expecting an event as a result of some
    # user interaction) we want to wait for it. The constants below set these wait period in which the
    # Vim UI is "blocked". Lower numbers will make Vim more responsive, but LLDB will be delayed and higher
    # numbers will mean that LLDB events are processed faster, but the Vim UI may appear less responsive at
    # times.
    eventDelayStep = 2
    eventDelayLaunch = 1
    eventDelayContinue = 1

    def __init__(self):
        """ Creates the LLDB SBDebugger object and initializes the UI class. """
        self.target = None
        self.process = None
        self.load_dependent_modules = True

        self.dbg = lldb.SBDebugger.Create()
        self.commandInterpreter = self.dbg.GetCommandInterpreter()

        self.ui = UI()

    def completeCommand(self, a, l, p):
        """ Returns a list of viable completions for command a with length l and cursor at p  """

        assert l[0] == 'L'
        # Remove first 'L' character that all commands start with
        l = l[1:]

        # Adjust length as string has 1 less character
        p = int(p) - 1

        result = lldb.SBStringList()
        num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result)

        if num == -1:
            # FIXME: insert completion character... what's a completion
            # character?
            pass
        elif num == -2:
            # FIXME: replace line with result.GetStringAtIndex(0)
            pass

        if result.GetSize() > 0:
            results = [_f for _f in [result.GetStringAtIndex(x)
                                    for x in range(result.GetSize())] if _f]
            return results
        else:
            return []

    def doStep(self, stepType):
        """ Perform a step command and block the UI for eventDelayStep seconds in order to process
            events on lldb's event queue.
            FIXME: if the step does not complete in eventDelayStep seconds, we relinquish control to
                   the main thread to avoid the appearance of a "hang". If this happens, the UI will
                   update whenever; usually when the user moves the cursor. This is somewhat annoying.
        """
        if not self.process:
            sys.stderr.write("No process to step")
            return

        t = self.process.GetSelectedThread()
        if stepType == StepType.INSTRUCTION:
            t.StepInstruction(False)
        if stepType == StepType.INSTRUCTION_OVER:
            t.StepInstruction(True)
        elif stepType == StepType.INTO:
            t.StepInto()
        elif stepType == StepType.OVER:
            t.StepOver()
        elif stepType == StepType.OUT:
            t.StepOut()

        self.processPendingEvents(self.eventDelayStep, True)

    def doSelect(self, command, args):
        """ Like doCommand, but suppress output when "select" is the first argument."""
        a = args.split(' ')
        return self.doCommand(command, args, "select" != a[0], True)

    def doProcess(self, args):
        """ Handle 'process' command. If 'launch' is requested, use doLaunch() instead
            of the command interpreter to start the inferior process.
        """
        a = args.split(' ')
        if len(args) == 0 or (len(a) > 0 and a[0] != 'launch'):
            self.doCommand("process", args)
            #self.ui.update(self.target, "", self)
        else:
            self.doLaunch('-s' not in args, "")

    def doAttach(self, process_name):
        """ Handle process attach.  """
        error = lldb.SBError()

        self.processListener = lldb.SBListener("process_event_listener")
        self.target = self.dbg.CreateTarget('')
        self.process = self.target.AttachToProcessWithName(
            self.processListener, process_name, False, error)
        if not error.Success():
            sys.stderr.write("Error during attach: " + str(error))
            return

        self.ui.activate()
        self.pid = self.process.GetProcessID()

        print("Attached to %s (pid=%d)" % (process_name, self.pid))

    def doDetach(self):
        if self.process is not None and self.process.IsValid():
            pid = self.process.GetProcessID()
            state = state_type_to_str(self.process.GetState())
            self.process.Detach()
            self.processPendingEvents(self.eventDelayLaunch)

    def doLaunch(self, stop_at_entry, args):
        """ Handle process launch.  """
        error = lldb.SBError()

        fs = self.target.GetExecutable()
        exe = os.path.join(fs.GetDirectory(), fs.GetFilename())
        if self.process is not None and self.process.IsValid():
            pid = self.process.GetProcessID()
            state = state_type_to_str(self.process.GetState())
            self.process.Destroy()

        launchInfo = lldb.SBLaunchInfo(args.split(' '))
        self.process = self.target.Launch(launchInfo, error)
        if not error.Success():
            sys.stderr.write("Error during launch: " + str(error))
            return

        # launch succeeded, store pid and add some event listeners
        self.pid = self.process.GetProcessID()
        self.processListener = lldb.SBListener("process_event_listener")
        self.process.GetBroadcaster().AddListener(
            self.processListener, lldb.SBProcess.eBroadcastBitStateChanged)

        print("Launched %s %s (pid=%d)" % (exe, args, self.pid))

        if not stop_at_entry:
            self.doContinue()
        else:
            self.processPendingEvents(self.eventDelayLaunch)

    def doTarget(self, args):
        """ Pass target command to interpreter, except if argument is not one of the valid options, or
            is create, in which case try to create a target with the argument as the executable. For example:
              target list        ==> handled by interpreter
              target create blah ==> custom creation of target 'blah'
              target blah        ==> also creates target blah
        """
        target_args = [  # "create",
            "delete",
            "list",
            "modules",
            "select",
            "stop-hook",
            "symbols",
            "variable"]

        a = args.split(' ')
        if len(args) == 0 or (len(a) > 0 and a[0] in target_args):
            self.doCommand("target", args)
            return
        elif len(a) > 1 and a[0] == "create":
            exe = a[1]
        elif len(a) == 1 and a[0] not in target_args:
            exe = a[0]

        err = lldb.SBError()
        self.target = self.dbg.CreateTarget(
            exe, None, None, self.load_dependent_modules, err)
        if not self.target:
            sys.stderr.write(
                "Error creating target %s. %s" %
                (str(exe), str(err)))
            return

        self.ui.activate()
        self.ui.update(self.target, "created target %s" % str(exe), self)

    def doContinue(self):
        """ Handle 'contiue' command.
            FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param.
        """
        if not self.process or not self.process.IsValid():
            sys.stderr.write("No process to continue")
            return

        self.process.Continue()
        self.processPendingEvents(self.eventDelayContinue)

    def doBreakpoint(self, args):
        """ Handle breakpoint command with command interpreter, except if the user calls
            "breakpoint" with no other args, in which case add a breakpoint at the line
            under the cursor.
        """
        a = args.split(' ')
        if len(args) == 0:
            show_output = False

            # User called us with no args, so toggle the bp under cursor
            cw = vim.current.window
            cb = vim.current.buffer
            name = cb.name
            line = cw.cursor[0]

            # Since the UI is responsbile for placing signs at bp locations, we have to
            # ask it if there already is one or more breakpoints at (file,
            # line)...
            if self.ui.haveBreakpoint(name, line):
                bps = self.ui.getBreakpoints(name, line)
                args = "delete %s" % " ".join([str(b.GetID()) for b in bps])
                self.ui.deleteBreakpoints(name, line)
            else:
                args = "set -f %s -l %d" % (name, line)
        else:
            show_output = True

        self.doCommand("breakpoint", args, show_output)
        return

    def doRefresh(self):
        """ process pending events and update UI on request """
        status = self.processPendingEvents()

    def doShow(self, name):
        """ handle :Lshow <name> """
        if not name:
            self.ui.activate()
            return

        if self.ui.showWindow(name):
            self.ui.update(self.target, "", self)

    def doHide(self, name):
        """ handle :Lhide <name> """
        if self.ui.hideWindow(name):
            self.ui.update(self.target, "", self)

    def doExit(self):
        self.dbg.Terminate()
        self.dbg = None

    def getCommandResult(self, command, command_args):
        """ Run cmd in the command interpreter and returns (success, output) """
        result = lldb.SBCommandReturnObject()
        cmd = "%s %s" % (command, command_args)

        self.commandInterpreter.HandleCommand(cmd, result)
        return (result.Succeeded(), result.GetOutput()
                if result.Succeeded() else result.GetError())

    def doCommand(
            self,
            command,
            command_args,
            print_on_success=True,
            goto_file=False):
        """ Run cmd in interpreter and print result (success or failure) on the vim status line. """
        (success, output) = self.getCommandResult(command, command_args)
        if success:
            self.ui.update(self.target, "", self, goto_file)
            if len(output) > 0 and print_on_success:
                print(output)
        else:
            sys.stderr.write(output)

    def getCommandOutput(self, command, command_args=""):
        """ runs cmd in the command interpreter andreturns (status, result) """
        result = lldb.SBCommandReturnObject()
        cmd = "%s %s" % (command, command_args)
        self.commandInterpreter.HandleCommand(cmd, result)
        return (result.Succeeded(), result.GetOutput()
                if result.Succeeded() else result.GetError())

    def processPendingEvents(self, wait_seconds=0, goto_file=True):
        """ Handle any events that are queued from the inferior.
            Blocks for at most wait_seconds, or if wait_seconds == 0,
            process only events that are already queued.
        """

        status = None
        num_events_handled = 0

        if self.process is not None:
            event = lldb.SBEvent()
            old_state = self.process.GetState()
            new_state = None
            done = False
            if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited:
                # Early-exit if we are in 'boring' states
                pass
            else:
                while not done and self.processListener is not None:
                    if not self.processListener.PeekAtNextEvent(event):
                        if wait_seconds > 0:
                            # No events on the queue, but we are allowed to wait for wait_seconds
                            # for any events to show up.
                            self.processListener.WaitForEvent(
                                wait_seconds, event)
                            new_state = lldb.SBProcess.GetStateFromEvent(event)

                            num_events_handled += 1

                        done = not self.processListener.PeekAtNextEvent(event)
                    else:
                        # An event is on the queue, process it here.
                        self.processListener.GetNextEvent(event)
                        new_state = lldb.SBProcess.GetStateFromEvent(event)

                        # continue if stopped after attaching
                        if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped:
                            self.process.Continue()

                        # If needed, perform any event-specific behaviour here
                        num_events_handled += 1

        if num_events_handled == 0:
            pass
        else:
            if old_state == new_state:
                status = ""
            self.ui.update(self.target, status, self, goto_file)
Example #9
0
class LLController(object):
  """ Handles LLDB events and commands. """

  # Timeout (sec) for waiting on new events. Due to the current single threaded design,
  # we should wait a few seconds for any new event, and update vim buffers. After which,
  # the user will have to manually request an update (using :LLrefresh).
  eventDelay = 2 # FIXME see processPendingEvents()

  def __init__(self, vifx):
    """ Creates the LLDB SBDebugger object and initializes the UI class.
        vifx represents the parent LLInterface object.
    """
    self.target = None
    self.process = None
    self.load_dependent_modules = True

    self.dbg = lldb.SBDebugger.Create()
    self.command_interpreter = self.dbg.GetCommandInterpreter()

    self.vifx = vifx
    self.ui = UI(vifx)

  def complete_command(self, arg, line, pos):
    """ Returns a list of viable completions for line, and cursor at pos. """

    result = lldb.SBStringList()
    num = self.command_interpreter.HandleCompletion(line, pos, 1, -1, result)

    if num == -1:
      # FIXME: insert completion character... what's a completion character?
      pass
    elif num == -2:
      # FIXME: replace line with result.GetStringAtIndex(0)
      pass

    if result.GetSize() > 0:
      results =  filter(None, [result.GetStringAtIndex(x) for x in range(result.GetSize())])
      return results
    else:
      return []

  def update_ui(self, status='', goto_file=False, buf=None):
    """ Update lldb buffers and signs placed in source files.
        @param status
            The message to be printed on success on the vim status line.
        @param goto_file
            Whether or not to move the cursor to the program counter (PC).
        @param buf
            If None, all buffers and signs excepts breakpoints would be updated.
            If '!all', all buffers incl. breakpoints would be updated.
            Otherwise, update only the specified buffer.
    """
    excl = ['breakpoints']
    commander = self.get_command_result
    if buf is None:
      self.ui.update(self.target, commander, status, goto_file, excl)
    elif buf == '!all':
      self.ui.update(self.target, commander, status, goto_file)
    else:
      self.ui.update_buffer(buf, self.target, commander)

  def do_frame(self, args):
    """ Handle 'frame' command. """
    self.exec_command("frame", args)
    if args.startswith('s'): # select
      self.update_ui(goto_file=True)

  def do_thread(self, args):
    """ Handle 'thread' command. """
    self.exec_command("thread", args)
    if args.startswith('se'): # select
      self.update_ui(goto_file=True)
    elif args[0] not in list('bil'): # not in backtrace, info, list
      self.processPendingEvents(self.eventDelay)

  def do_process(self, args):
    """ Handle 'process' command. """
    # FIXME use do_attach/do_detach to handle attach/detach subcommands.
    if args.startswith("la"): # launch
      if self.process is not None and self.process.IsValid():
        pid = self.process.GetProcessID()
        self.process.Destroy()

      (success, result) = self.get_command_result("process", args)
      self.process = self.target.process
      if not success:
        self.vifx.log("Error during launch: " + str(result))
        return

      # launch succeeded, store pid and add some event listeners
      self.pid = self.process.GetProcessID()
      self.processListener = lldb.SBListener("process_event_listener")
      self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged)

      self.vifx.log("%s" % result, 0)
    elif args.startswith("i"): # interrupt
      if not self.process or not self.process.IsValid():
        self.vifx.log("No valid process to interrupt.")
        return
      self.process.SendAsyncInterrupt()
    elif args.startswith("k"): # kill
      if not self.process or not self.process.IsValid():
        self.vifx.log("No valid process to kill.")
        return
      if not self.process.Destroy().Success():
        self.vifx.log("Error during kill: " + str(error))
      else:
        self.vifx.log("Killed process (pid=%d)" % self.pid)
    else:
      self.exec_command("process", args)

    self.processPendingEvents(self.eventDelay)

  def do_attach(self, process_name):
    """ Handle process attach. """
    (success, result) = self.get_command_result("attach", args)
    self.target = self.dbg.GetSelectedTarget()
    if not success:
      self.vifx.log("Error during attach: " + str(result))
      return

    # attach succeeded, initialize variables, listeners
    self.process = self.target.process
    self.pid = self.process.GetProcessID()
    self.processListener = lldb.SBListener("process_event_listener")
    self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged)
    self.vifx.log(str(result), 0)

  def do_detach(self):
    """ Handle process detach. """
    if self.process is not None and self.process.IsValid():
      pid = self.process.GetProcessID()
      self.process.Detach()
      self.processPendingEvents(self.eventDelay)

  def do_target(self, args):
    """ Handle 'target' command. """
    (success, result) = self.get_command_result("target", args)
    if not success:
      self.vifx.log(str(result))
    elif args.startswith('c'): # create
      self.target = self.dbg.GetSelectedTarget()
      self.vifx.log(str(result), 0)
      self.processPendingEvents(self.eventDelay)

  def do_command(self, args):
    """ Handle 'command' command. """
    self.ctrl.exec_command("command", args)
    if args.startswith('so'): # source
      self.processPendingEvents(self.eventDelay)
      self.update_ui(buf='breakpoints')

  def do_breakswitch(self, bufnr, line):
    """ Switch breakpoint at the specified line in the buffer. """
    key = (bufnr, line)
    if self.ui.bp_list.has_key(key):
      bps = self.ui.bp_list[key]
      args = "delete %s" % " ".join([str(b.GetID()) for b in bps])
    else:
      path = self.vifx.get_buffer_from_nr(bufnr).name
      args = "set -f %s -l %d" % (path, line)
    self.do_breakpoint(args)

  def do_breakpoint(self, args):
    """ Handle breakpoint command with command interpreter. """
    self.exec_command("breakpoint", args)
    self.update_ui(buf="breakpoints")

  def do_refresh(self):
    """ Process pending events and update UI on request. """
    status = self.processPendingEvents()

  def do_exit(self):
    """ Destroy the debugger instance. """
    self.dbg.Terminate()
    self.dbg = None

  def get_command_result(self, command, args=""):
    """ Run command in the command interpreter and returns (success, output) """
    result = lldb.SBCommandReturnObject()
    cmd = "%s %s" % (command, args)

    self.command_interpreter.HandleCommand(cmd, result)
    return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError())

  def exec_command(self, command, args, update_level=0, goto_file=False):
    """ Run command in the interpreter and:
        + Print result on the vim status line (update_level >= 0)
        + Update UI (update_level >= 1)
        + Check for and process any lldb events in queue (update_level >= 2)
    """
    (success, output) = self.get_command_result(command, args)
    if success:
      if update_level == 0 and len(output) > 0:
        self.vifx.log(output, 0)
      if update_level == 1:
        self.update_ui(output, goto_file)
      elif update_level > 1:
        self.processPendingEvents(self.eventDelay, goto_file)
    else:
      self.vifx.log(output)

  def processPendingEvents(self, wait_seconds=0, goto_file=True): # FIXME replace this with a separate thread
    """ Handle any events that are queued from the inferior.
        Blocks for at most wait_seconds, or if wait_seconds == 0,
        process only events that are already queued.
    """

    num_events_handled = 0

    if self.process is not None:
      event = lldb.SBEvent()
      old_state = self.process.GetState()
      new_state = None
      done = False
      if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited:
        # Early-exit if we are in 'boring' states
        pass
      else:
        while not done and self.processListener is not None:
          if not self.processListener.PeekAtNextEvent(event):
            if wait_seconds > 0:
              # No events on the queue, but we are allowed to wait for wait_seconds
              # for any events to show up.
              self.processListener.WaitForEvent(wait_seconds, event)
              new_state = lldb.SBProcess.GetStateFromEvent(event)

              num_events_handled += 1

            done = not self.processListener.PeekAtNextEvent(event)
          else:
            # An event is on the queue, process it here.
            self.processListener.GetNextEvent(event)
            new_state = lldb.SBProcess.GetStateFromEvent(event)

            # continue if stopped after attaching
            if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped:
              self.process.Continue()

            # If needed, perform any event-specific behaviour here
            num_events_handled += 1

    self.update_ui(goto_file=goto_file, buf='!all')