diff options
Diffstat (limited to 'lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py')
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py | 741 |
1 files changed, 385 insertions, 356 deletions
diff --git a/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py b/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py index 923e771e6af..ed54633a3ea 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py @@ -3,7 +3,9 @@ # This file defines the layer that talks to lldb # -import os, re, sys +import os +import re +import sys import lldb import vim from vim_ui import UI @@ -13,372 +15,399 @@ from vim_ui import UI # ================================================= # Shamelessly copy/pasted from lldbutil.py in the test suite -def state_type_to_str(enum): - """Returns the stateType string given an enum.""" - if enum == lldb.eStateInvalid: - return "invalid" - elif enum == lldb.eStateUnloaded: - return "unloaded" - elif enum == lldb.eStateConnected: - return "connected" - elif enum == lldb.eStateAttaching: - return "attaching" - elif enum == lldb.eStateLaunching: - return "launching" - elif enum == lldb.eStateStopped: - return "stopped" - elif enum == lldb.eStateRunning: - return "running" - elif enum == lldb.eStateStepping: - return "stepping" - elif enum == lldb.eStateCrashed: - return "crashed" - elif enum == lldb.eStateDetached: - return "detached" - elif enum == lldb.eStateExited: - return "exited" - elif enum == lldb.eStateSuspended: - return "suspended" - else: - raise Exception("Unknown StateType enum") -class StepType: - INSTRUCTION = 1 - INSTRUCTION_OVER = 2 - INTO = 3 - OVER = 4 - OUT = 5 -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 = 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 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() +def state_type_to_str(enum): + """Returns the stateType string given an enum.""" + if enum == lldb.eStateInvalid: + return "invalid" + elif enum == lldb.eStateUnloaded: + return "unloaded" + elif enum == lldb.eStateConnected: + return "connected" + elif enum == lldb.eStateAttaching: + return "attaching" + elif enum == lldb.eStateLaunching: + return "launching" + elif enum == lldb.eStateStopped: + return "stopped" + elif enum == lldb.eStateRunning: + return "running" + elif enum == lldb.eStateStepping: + return "stepping" + elif enum == lldb.eStateCrashed: + return "crashed" + elif enum == lldb.eStateDetached: + return "detached" + elif enum == lldb.eStateExited: + return "exited" + elif enum == lldb.eStateSuspended: + return "suspended" 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 + raise Exception("Unknown StateType enum") - 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. - """ +class StepType: + INSTRUCTION = 1 + INSTRUCTION_OVER = 2 + INTO = 3 + OVER = 4 + OUT = 5 - 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) + +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 = 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 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) def returnCompleteCommand(a, l, p): - """ Returns a "\n"-separated string with possible completion results - for command a with length l and cursor at p. - """ - separator = "\n" - results = ctrl.completeCommand(a, l, p) - vim.command('return "%s%s"' % (separator.join(results), separator)) + """ Returns a "\n"-separated string with possible completion results + for command a with length l and cursor at p. + """ + separator = "\n" + results = ctrl.completeCommand(a, l, p) + vim.command('return "%s%s"' % (separator.join(results), separator)) + def returnCompleteWindow(a, l, p): - """ Returns a "\n"-separated string with possible completion results - for commands that expect a window name parameter (like hide/show). - FIXME: connect to ctrl.ui instead of hardcoding the list here - """ - separator = "\n" - results = ['breakpoints', 'backtrace', 'disassembly', 'locals', 'threads', 'registers'] - vim.command('return "%s%s"' % (separator.join(results), separator)) + """ Returns a "\n"-separated string with possible completion results + for commands that expect a window name parameter (like hide/show). + FIXME: connect to ctrl.ui instead of hardcoding the list here + """ + separator = "\n" + results = [ + 'breakpoints', + 'backtrace', + 'disassembly', + 'locals', + 'threads', + 'registers'] + vim.command('return "%s%s"' % (separator.join(results), separator)) global ctrl ctrl = LLDBController() |