blob: 77f556361c7e44e5be6aed7d7347508ee1b7ea47 [file] [log] [blame]
Daniel Maleaac4ce0c2013-02-11 17:18:14 +00001
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 doLaunch(self, stop_at_entry, args):
145 """ Handle process launch. """
146 error = lldb.SBError()
147
148 fs = self.target.GetExecutable()
149 exe = os.path.join(fs.GetDirectory(), fs.GetFilename())
150 if self.process is not None and self.process.IsValid():
151 pid = self.process.GetProcessID()
152 state = state_type_to_str(self.process.GetState())
153 self.process.Destroy()
154
155 launchInfo = lldb.SBLaunchInfo(args.split(' '))
156 self.process = self.target.Launch(launchInfo, error)
157 if not error.Success():
158 sys.stderr.write("Error during launch: " + str(error))
159 return
160
161 # launch succeeded, store pid and add some event listeners
162 self.pid = self.process.GetProcessID()
163 self.processListener = lldb.SBListener("process_event_listener")
164 self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged)
165
166 print "Launched %s %s (pid=%d)" % (exe, args, self.pid)
167
168 if not stop_at_entry:
169 self.doContinue()
170 else:
171 self.processPendingEvents(self.eventDelayLaunch)
172
173 def doTarget(self, args):
174 """ Pass target command to interpreter, except if argument is not one of the valid options, or
175 is create, in which case try to create a target with the argument as the executable. For example:
176 target list ==> handled by interpreter
177 target create blah ==> custom creation of target 'blah'
178 target blah ==> also creates target blah
179 """
180 target_args = [#"create",
181 "delete",
182 "list",
183 "modules",
184 "select",
185 "stop-hook",
186 "symbols",
187 "variable"]
188
189 a = args.split(' ')
190 if len(args) == 0 or (len(a) > 0 and a[0] in target_args):
191 self.doCommand("target", args)
192 return
193 elif len(a) > 1 and a[0] == "create":
194 exe = a[1]
195 elif len(a) == 1 and a[0] not in target_args:
196 exe = a[0]
197
198 err = lldb.SBError()
199 self.target = self.dbg.CreateTarget(exe, None, None, self.load_dependent_modules, err)
200 if not self.target:
201 sys.stderr.write("Error creating target %s. %s" % (str(exe), str(err)))
202 return
203
204 self.ui.activate()
205 self.ui.update(self.target, "created target %s" % str(exe), self)
206
207 def doContinue(self):
208 """ Handle 'contiue' command.
209 FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param.
210 """
211 if not self.process or not self.process.IsValid():
212 sys.stderr.write("No process to continue")
213 return
214
215 self.process.Continue()
216 self.processPendingEvents(self.eventDelayContinue)
217
218 def doBreakpoint(self, args):
219 """ Handle breakpoint command with command interpreter, except if the user calls
220 "breakpoint" with no other args, in which case add a breakpoint at the line
221 under the cursor.
222 """
223 a = args.split(' ')
224 if len(args) == 0:
225 show_output = False
226
227 # User called us with no args, so toggle the bp under cursor
228 cw = vim.current.window
229 cb = vim.current.buffer
230 name = cb.name
231 line = cw.cursor[0]
232
233 # Since the UI is responsbile for placing signs at bp locations, we have to
234 # ask it if there already is one or more breakpoints at (file, line)...
235 if self.ui.haveBreakpoint(name, line):
236 bps = self.ui.getBreakpoints(name, line)
237 args = "delete %s" % " ".join([str(b.GetID()) for b in bps])
238 self.ui.deleteBreakpoints(name, line)
239 else:
240 args = "set -f %s -l %d" % (name, line)
241 else:
242 show_output = True
243
244 self.doCommand("breakpoint", args, show_output)
245 return
246
247 def doRefresh(self):
248 """ process pending events and update UI on request """
249 status = self.processPendingEvents()
250
251 def doShow(self, name):
252 """ handle :Lshow <name> """
253 if self.ui.showWindow(name):
254 self.ui.update(self.target, "", self)
255
256 def doHide(self, name):
257 """ handle :Lhide <name> """
258 if self.ui.hideWindow(name):
259 self.ui.update(self.target, "", self)
260
261 def doExit(self):
262 self.dbg.Terminate()
263 self.dbg = None
264
265 def getCommandResult(self, command, command_args):
266 """ Run cmd in the command interpreter and returns (success, output) """
267 result = lldb.SBCommandReturnObject()
268 cmd = "%s %s" % (command, command_args)
269
270 self.commandInterpreter.HandleCommand(cmd, result)
271 return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError())
272
273 def doCommand(self, command, command_args, print_on_success = True, goto_file=False):
274 """ Run cmd in interpreter and print result (success or failure) on the vim status line. """
275 (success, output) = self.getCommandResult(command, command_args)
276 if success:
277 self.ui.update(self.target, "", self, goto_file)
278 if len(output) > 0 and print_on_success:
279 print output
280 else:
281 sys.stderr.write(output)
282
283 def getCommandOutput(self, command, command_args=""):
284 """ runs cmd in the command interpreter andreturns (status, result) """
285 result = lldb.SBCommandReturnObject()
286 cmd = "%s %s" % (command, command_args)
287 self.commandInterpreter.HandleCommand(cmd, result)
288 return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError())
289
290 def processPendingEvents(self, wait_seconds=0, goto_file=True):
291 """ Handle any events that are queued from the inferior.
292 Blocks for at most wait_seconds, or if wait_seconds == 0,
293 process only events that are already queued.
294 """
295
296 status = None
297 num_events_handled = 0
298
299 if self.process is not None:
300 event = lldb.SBEvent()
301 old_state = self.process.GetState()
302 new_state = None
303 done = False
304 if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited:
305 # Early-exit if we are in 'boring' states
306 pass
307 else:
308 while not done and self.processListener is not None:
309 if not self.processListener.PeekAtNextEvent(event):
310 if wait_seconds > 0:
311 # No events on the queue, but we are allowed to wait for wait_seconds
312 # for any events to show up.
313 self.processListener.WaitForEvent(wait_seconds, event)
314 new_state = lldb.SBProcess.GetStateFromEvent(event)
315
316 num_events_handled += 1
317
318 done = not self.processListener.PeekAtNextEvent(event)
319 else:
320 # An event is on the queue, process it here.
321 self.processListener.GetNextEvent(event)
322 new_state = lldb.SBProcess.GetStateFromEvent(event)
323
324 # If needed, perform any event-specific behaviour here
325 num_events_handled += 1
326
327 if num_events_handled == 0:
328 pass
329 else:
330 if old_state == new_state:
331 status = ""
332 self.ui.update(self.target, status, self, goto_file)
333
334
335def returnCompleteCommand(a, l, p):
336 """ Returns a "\n"-separated string with possible completion results
337 for command a with length l and cursor at p.
338 """
339 separator = "\n"
340 results = ctrl.completeCommand(a, l, p)
341 vim.command('return "%s%s"' % (separator.join(results), separator))
342
343def returnCompleteWindow(a, l, p):
344 """ Returns a "\n"-separated string with possible completion results
345 for commands that expect a window name parameter (like hide/show).
346 FIXME: connect to ctrl.ui instead of hardcoding the list here
347 """
348 separator = "\n"
349 results = ['breakpoints', 'backtrace', 'disassembly', 'locals', 'threads', 'registers']
350 vim.command('return "%s%s"' % (separator.join(results), separator))
351
352global ctrl
353ctrl = LLDBController()