blob: 923e771e6afe288249c7627d56f5150d2871d07c [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
Daniel Maleaa7c86e12013-05-22 16:04:28 +0000144 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()
Daniel Maleaa7c86e12013-05-22 16:04:28 +0000156 self.pid = self.process.GetProcessID()
Daniel Maleaa7c86e12013-05-22 16:04:28 +0000157
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
Daniel Maleaac4ce0c2013-02-11 17:18:14 +0000167 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> """
Daniel Malea2e88c6b62013-05-22 16:05:55 +0000276 if not name:
277 self.ui.activate()
278 return
279
Daniel Maleaac4ce0c2013-02-11 17:18:14 +0000280 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
Daniel Maleabb437212013-05-28 21:27:03 +0000351 # continue if stopped after attaching
352 if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped:
353 self.process.Continue()
354
Daniel Maleaac4ce0c2013-02-11 17:18:14 +0000355 # 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()