Home | History | Annotate | Download | only in python-vim-lldb
      1 
      2 #
      3 # This file defines the layer that talks to lldb
      4 #
      5 
      6 import os, re, sys
      7 import lldb
      8 import vim
      9 from 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
     16 def 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 
     45 class StepType:
     46   INSTRUCTION = 1
     47   INSTRUCTION_OVER = 2
     48   INTO = 3
     49   OVER = 4
     50   OUT = 5
     51 
     52 class 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 
    366 def 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 
    374 def 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 
    383 global ctrl
    384 ctrl = LLDBController()
    385