Skip to content
This repository was archived by the owner on May 21, 2019. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 88cd3fd

Browse files
author
Daniel Malea
committedFeb 11, 2013
Add Vim frontend to LLDB.
- Access any LLDB CLI command in Vim by typing ":L<command>". Tab-completion works too! - See source locations for breakpoints and the current PC with vim "marks" and highlights. - Examine backtraces, locals, disassembly, registers, and breakpoints in dedicated Vim windows. - See when in-scope variables and registers change by watching for (red) highlights. This plugin opens multiple Vim "windows" to display debugger information. To quit all windows at the same time use ":qa". The alternative would be ":q" to close each window separately. This plugin is known to work on Mac OS X (Mountain Lion) with MacVim and the system-provided terminal Vim, and on Linux (Ubuntu 12.04 and 12.10) with GVim and the terminal Vim from the "vim-gnome" package. git-svn-id: https://llvm.org/svn/llvm-project/lldb/trunk@174892 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 17ab954 commit 88cd3fd

File tree

9 files changed

+1669
-0
lines changed

9 files changed

+1669
-0
lines changed
 

‎utils/vim-lldb/README

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
=================
3+
LLDB Vim Frontend
4+
=================
5+
6+
Prerequisites
7+
-------------
8+
9+
This plugin is known to work with the following flavours of Vim:
10+
11+
* Linux (tested on Ubuntu 12.04/12.10):
12+
* vim/gvim (from vim-gnome package version 7.3)
13+
14+
* Mac OS X (tested on Mountain Lion)
15+
* Vim command-line (7.3 from Xcode)
16+
* MacVim 7.3
17+
18+
To install the plugin, ensure you have
19+
* a working version of lldb on your path, or the environment variable LLDB
20+
pointing to the lldb binary you would like to use.
21+
* a python-enabled vim (check with ":python print 2")
22+
23+
24+
Installation
25+
------------
26+
27+
1) Install the Vim pathogen plugin (it keeps installed plugins organized):
28+
29+
https://github.com/tpope/vim-pathogen
30+
31+
Or, for the impatient:
32+
33+
mkdir -p ~/.vim/autoload ~/.vim/bundle; \
34+
curl -Sso ~/.vim/autoload/pathogen.vim \
35+
https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim
36+
37+
2) Symlink (or copy) ~/.vim/bundle/vim-lldb to this directory:
38+
39+
ln -sf <lldb-dir>/utils/vim-lldb ~/.vim/bundle/vim-lldb
40+
41+
3) Update your help-tags. Start vim, do:
42+
43+
:Helptags
44+
45+
4) Have fun!
46+
47+
48+
Usage/Getting Help
49+
------------------
50+
All LLDB commands (with tab-completion) can be accessed in Vim's
51+
command mode. Try it out by typing:
52+
53+
:L<tab>
54+
55+
There are several sources of help available:
56+
57+
:help lldb -- Documentation for this plugin
58+
:Lhelp -- LLDB's built-in help system (i.e lldb 'help' command)
59+
:Lscript help (lldb) -- Complete LLDB Python API reference

‎utils/vim-lldb/doc/lldb.txt

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
*lldb.txt* A plugin that enables debugging from your favourite editor
2+
3+
Author: Daniel Malea <daniel.malea@intel.com>
4+
License: Same terms as Vim itself (see |license|)
5+
6+
INTRODUCTION *lldb*
7+
8+
Installing this plugin enables a set of commands in Vim to control the
9+
LLDB (http://lldb.llvm.org) debugger.
10+
11+
COMMANDS *lldb-commands*
12+
13+
The LLDB command interpreter is exposed to Vim's command mode using the
14+
':L' prefix. Tab-completion is available and will cycle through commands.
15+
Some commands have modified behaviour in Vim; for example, :Lbreakpoint
16+
with no arguments will set a breakpoint at the current cursor, rather than
17+
printing the standard help information for the LLDB command 'breakpoint'.
18+
19+
*lldb-windows*
20+
21+
In addition to the standard commands available under the LLDB interpreter,
22+
there are also commands to display or hide informational debugger panes.
23+
24+
Windows can be shown or hidden using the ':Lhide <name>' or ':Lshow <name>'
25+
commands.
26+
*lldb-:Lhide*
27+
:Lhide [windowname] Hide informational debugger pane named 'windowname'.
28+
29+
*lldb-:Lshow*
30+
:Lshow [windowname] Show informational debugger pane named 'windowname'.
31+
32+
Possible window name arguments to the Lhide and Lshow commands include:
33+
34+
* backtrace
35+
* breakpoints
36+
* disassembly
37+
* locals
38+
* registers
39+
* threads
40+
*lldb-:Ltarget*
41+
:Ltarget [[create] executable]
42+
Create a target with the specified executable. If
43+
run with a single argument, that argument is assumed
44+
to be a path to the executable to be debugged.
45+
Otherwise, all arguments are passed into LLDB's command
46+
interpreter.
47+
48+
*lldb-:Lstart*
49+
:Lstart Create a process by executing the current target
50+
and wait for LLDB to attach.
51+
52+
*lldb-:Lrun*
53+
:Lrun Create a process by executing the current target
54+
without waiting for LLDB to attach.
55+
56+
*lldb-:Lcontinue*
57+
:Lcontinue Continue execution of the process until the next
58+
breakpoint is hit or the process exits.
59+
60+
*lldb-:Lthread*
61+
:Lthread <args> Passes through to LLDB. See :Lhelp thread.
62+
63+
*lldb-:Lstep*
64+
:Lstep Step into the current function call.
65+
66+
*lldb-:Lstepin*
67+
:Lstepin Step into the current function call.
68+
69+
*lldb-:Lstepinst*
70+
:Lstepinst Step one instruction.
71+
72+
*lldb-:Lstepinstover*
73+
:Lstepinstover Step one instruction, but skip over jump or call
74+
instructions.
75+
76+
*lldb-:Lnext*
77+
:Lnext Step to the next line.
78+
79+
*lldb-:Lfinish*
80+
:Lfinish Step out of the current function.
81+
82+
*lldb-:Lbreakpoint*
83+
:Lbreakpoint [args] When arguments are provided, the lldb breakpoint
84+
command is invoked. If no arguments are provided,
85+
a breakpoint at the location under the cursor.
86+
87+
MAPPINGS *lldb-mappings*
88+
89+
On Mac OS X (under MacVim) , the following key mappings are available:
90+
91+
<Command-B> Insert a breakpoint at the line under cursor
92+
93+
94+
ABOUT *lldb-about*
95+
96+
Grab the latest version of this plugin (and LLDB sources) with:
97+
git clone http://llvm.org/git/lldb
98+
99+
File any bugs at:
100+
http://llvm.org/bugs/enter_bug.cgi?product=lldb
101+
102+
vim:tw=78:et:ft=help:norl:

‎utils/vim-lldb/plugin/lldb.vim

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
" Vim script glue code for LLDB integration
3+
4+
function! s:FindPythonScriptDir()
5+
for dir in pathogen#split(&runtimepath)
6+
let searchstr = "python-vim-lldb"
7+
let candidates = pathogen#glob_directories(dir . "/" . searchstr)
8+
if len(candidates) > 0
9+
return candidates[0]
10+
endif
11+
endfor
12+
return
13+
endfunction()
14+
15+
function! s:InitLldbPlugin()
16+
if has('python') == 0
17+
call confirm('ERROR: This Vim installation does not have python support. lldb.vim will not work.')
18+
return
19+
endif
20+
21+
" Key-Bindings
22+
" FIXME: choose sensible keybindings for:
23+
" - process: start, interrupt, continue, continue-to-cursor
24+
" - step: instruction, in, over, out
25+
"
26+
if has('gui_macvim')
27+
" Apple-B toggles breakpoint on cursor
28+
map <D-B> :Lbreakpoint<CR>
29+
endif
30+
31+
"
32+
" Setup the python interpreter path
33+
"
34+
let vim_lldb_pydir = s:FindPythonScriptDir()
35+
execute 'python import sys; sys.path.append("' . vim_lldb_pydir . '")'
36+
37+
"
38+
" Register :L<Command>
39+
" The LLDB CommandInterpreter provides tab-completion in Vim's command mode.
40+
" FIXME: this list of commands, at least partially should be auto-generated
41+
"
42+
43+
" Window show/hide commands
44+
command -complete=custom,s:CompleteWindow -nargs=1 Lhide python ctrl.doHide('<args>')
45+
command -complete=custom,s:CompleteWindow -nargs=1 Lshow python ctrl.doShow('<args>')
46+
47+
" Launching convenience commands (no autocompletion)
48+
command -nargs=* Lstart python ctrl.doLaunch(True, '<args>')
49+
command -nargs=* Lrun python ctrl.doLaunch(False, '<args>')
50+
51+
" Regexp-commands: because vim's command mode does not support '_' or '-'
52+
" characters in command names, we omit them when creating the :L<cmd>
53+
" equivalents.
54+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpattach python ctrl.doCommand('_regexp-attach', '<args>')
55+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpbreak python ctrl.doCommand('_regexp-break', '<args>')
56+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpbt python ctrl.doCommand('_regexp-bt', '<args>')
57+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpdown python ctrl.doCommand('_regexp-down', '<args>')
58+
command -complete=custom,s:CompleteCommand -nargs=* Lregexptbreak python ctrl.doCommand('_regexp-tbreak', '<args>')
59+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpdisplay python ctrl.doCommand('_regexp-display', '<args>')
60+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpundisplay python ctrl.doCommand('_regexp-undisplay', '<args>')
61+
command -complete=custom,s:CompleteCommand -nargs=* Lregexpup python ctrl.doCommand('_regexp-up', '<args>')
62+
63+
command -complete=custom,s:CompleteCommand -nargs=* Lapropos python ctrl.doCommand('apropos', '<args>')
64+
command -complete=custom,s:CompleteCommand -nargs=* Lbacktrace python ctrl.doCommand('bt', '<args>')
65+
command -complete=custom,s:CompleteCommand -nargs=* Lbreakpoint python ctrl.doBreakpoint('<args>')
66+
command -complete=custom,s:CompleteCommand -nargs=* Lcommand python ctrl.doCommand('command', '<args>')
67+
command -complete=custom,s:CompleteCommand -nargs=* Ldisassemble python ctrl.doCommand('disassemble', '<args>')
68+
command -complete=custom,s:CompleteCommand -nargs=* Lexpression python ctrl.doCommand('expression', '<args>')
69+
command -complete=custom,s:CompleteCommand -nargs=* Lhelp python ctrl.doCommand('help', '<args>')
70+
command -complete=custom,s:CompleteCommand -nargs=* Llog python ctrl.doCommand('log', '<args>')
71+
command -complete=custom,s:CompleteCommand -nargs=* Lplatform python ctrl.doCommand('platform','<args>')
72+
command -complete=custom,s:CompleteCommand -nargs=* Lplugin python ctrl.doCommand('plugin', '<args>')
73+
command -complete=custom,s:CompleteCommand -nargs=* Lprocess python ctrl.doProcess('<args>')
74+
command -complete=custom,s:CompleteCommand -nargs=* Lregister python ctrl.doCommand('register', '<args>')
75+
command -complete=custom,s:CompleteCommand -nargs=* Lscript python ctrl.doCommand('script', '<args>')
76+
command -complete=custom,s:CompleteCommand -nargs=* Lsettings python ctrl.doCommand('settings','<args>')
77+
command -complete=custom,s:CompleteCommand -nargs=* Lsource python ctrl.doCommand('source', '<args>')
78+
command -complete=custom,s:CompleteCommand -nargs=* Ltype python ctrl.doCommand('type', '<args>')
79+
command -complete=custom,s:CompleteCommand -nargs=* Lversion python ctrl.doCommand('version', '<args>')
80+
command -complete=custom,s:CompleteCommand -nargs=* Lwatchpoint python ctrl.doCommand('watchpoint', '<args>')
81+
82+
" Convenience (shortcut) LLDB commands
83+
command -complete=custom,s:CompleteCommand -nargs=* Lprint python ctrl.doCommand('print', '<args>')
84+
command -complete=custom,s:CompleteCommand -nargs=* Lbt python ctrl.doCommand('bt', '<args>')
85+
86+
" Frame/Thread-Selection (commands that also do an Uupdate but do not
87+
" generate events in LLDB)
88+
command -complete=custom,s:CompleteCommand -nargs=* Lframe python ctrl.doSelect('frame', '<args>')
89+
command -complete=custom,s:CompleteCommand -nargs=? Lup python ctrl.doCommand('up', '<args>', print_on_success=False, goto_file=True)
90+
command -complete=custom,s:CompleteCommand -nargs=? Ldown python ctrl.doCommand('down', '<args>', print_on_success=False, goto_file=True)
91+
command -complete=custom,s:CompleteCommand -nargs=* Lthread python ctrl.doSelect('thread', '<args>')
92+
93+
command -complete=custom,s:CompleteCommand -nargs=* Ltarget python ctrl.doTarget('<args>')
94+
95+
" Continue
96+
command -complete=custom,s:CompleteCommand -nargs=* Lcontinue python ctrl.doContinue()
97+
98+
" Thread-Stepping (no autocompletion)
99+
command -nargs=0 Lstepinst python ctrl.doStep(StepType.INSTRUCTION)
100+
command -nargs=0 Lstepinstover python ctrl.doStep(StepType.INSTRUCTION_OVER)
101+
command -nargs=0 Lstepin python ctrl.doStep(StepType.INTO)
102+
command -nargs=0 Lstep python ctrl.doStep(StepType.INTO)
103+
command -nargs=0 Lnext python ctrl.doStep(StepType.OVER)
104+
command -nargs=0 Lfinish python ctrl.doStep(StepType.OUT)
105+
106+
" hack: service the LLDB event-queue when the cursor moves
107+
" FIXME: some threaded solution would be better...but it
108+
" would have to be designed carefully because Vim's APIs are non threadsafe;
109+
" use of the vim module **MUST** be restricted to the main thread.
110+
command -nargs=0 Lrefresh python ctrl.doRefresh()
111+
autocmd CursorMoved * :Lrefresh
112+
autocmd CursorHold * :Lrefresh
113+
autocmd VimLeavePre * python ctrl.doExit()
114+
115+
execute 'pyfile ' . vim_lldb_pydir . '/plugin.py'
116+
endfunction()
117+
118+
function! s:CompleteCommand(A, L, P)
119+
python << EOF
120+
a = vim.eval("a:A")
121+
l = vim.eval("a:L")
122+
p = vim.eval("a:P")
123+
returnCompleteCommand(a, l, p)
124+
EOF
125+
endfunction()
126+
127+
function! s:CompleteWindow(A, L, P)
128+
python << EOF
129+
a = vim.eval("a:A")
130+
l = vim.eval("a:L")
131+
p = vim.eval("a:P")
132+
returnCompleteWindow(a, l, p)
133+
EOF
134+
endfunction()
135+
136+
call s:InitLldbPlugin()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
# Locate and load the lldb python module
3+
4+
import os, sys
5+
6+
def import_lldb():
7+
""" Find and import the lldb modules. This function tries to find the lldb module by:
8+
1. Simply by doing "import lldb" in case the system python installation is aware of lldb. If that fails,
9+
2. Executes the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb
10+
on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid
11+
path, it is added to sys.path and the import is attempted again. If that fails, 3. On Mac OS X the
12+
default Xcode 4.5 installation path.
13+
"""
14+
15+
# Try simple 'import lldb', in case of a system-wide install or a pre-configured PYTHONPATH
16+
try:
17+
import lldb
18+
return True
19+
except ImportError:
20+
pass
21+
22+
# Allow overriding default path to lldb executable with the LLDB environment variable
23+
lldb_executable = 'lldb'
24+
if 'LLDB' in os.environ and os.path.exists(os.environ['LLDB']):
25+
lldb_executable = os.environ['LLDB']
26+
27+
# Try using builtin module location support ('lldb -P')
28+
from subprocess import check_output, CalledProcessError
29+
try:
30+
with open(os.devnull, 'w') as fnull:
31+
lldb_minus_p_path = check_output("%s -P" % lldb_executable, shell=True, stderr=fnull).strip()
32+
if not os.path.exists(lldb_minus_p_path):
33+
#lldb -P returned invalid path, probably too old
34+
pass
35+
else:
36+
sys.path.append(lldb_minus_p_path)
37+
import lldb
38+
return True
39+
except CalledProcessError:
40+
# Cannot run 'lldb -P' to determine location of lldb python module
41+
pass
42+
except ImportError:
43+
# Unable to import lldb module from path returned by `lldb -P`
44+
pass
45+
46+
# On Mac OS X, use the try the default path to XCode lldb module
47+
if "darwin" in sys.platform:
48+
xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python/"
49+
sys.path.append(xcode_python_path)
50+
try:
51+
import lldb
52+
return True
53+
except ImportError:
54+
# Unable to import lldb module from default Xcode python path
55+
pass
56+
57+
return False
58+
59+
if not import_lldb():
60+
import vim
61+
vim.command('redraw | echo "%s"' % " Error loading lldb module; vim-lldb will be disabled. Check LLDB installation or set LLDB environment variable.")
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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 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+
335+
def 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+
343+
def 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+
352+
global ctrl
353+
ctrl = LLDBController()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
# Try to import all dependencies, catch and handle the error gracefully if it fails.
3+
4+
import import_lldb
5+
6+
try:
7+
import lldb
8+
import vim
9+
except ImportError:
10+
sys.stderr.write("Unable to load vim/lldb module. Check lldb is on the path is available (or LLDB is set) and that script is invoked inside Vim with :pyfile")
11+
pass
12+
else:
13+
# Everthing went well, so use import to start the plugin controller
14+
from lldb_controller import *

‎utils/vim-lldb/python-vim-lldb/vim_panes.py

Lines changed: 636 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
2+
# Classes responsible for drawing signs in the Vim user interface.
3+
4+
import vim
5+
6+
class VimSign(object):
7+
SIGN_TEXT_BREAKPOINT_RESOLVED = "B>"
8+
SIGN_TEXT_BREAKPOINT_UNRESOLVED = "b>"
9+
SIGN_TEXT_PC = "->"
10+
SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue'
11+
12+
# unique sign id (for ':[sign/highlight] define)
13+
sign_id = 1
14+
15+
# unique name id (for ':sign place')
16+
name_id = 1
17+
18+
# Map of {(sign_text, highlight_colour) --> sign_name}
19+
defined_signs = {}
20+
21+
def __init__(self, sign_text, buffer, line_number, highlight_colour=None):
22+
""" Define the sign and highlight (if applicable) and show the sign. """
23+
24+
# Get the sign name, either by defining it, or looking it up in the map of defined signs
25+
key = (sign_text, highlight_colour)
26+
if not key in VimSign.defined_signs:
27+
name = self.define(sign_text, highlight_colour)
28+
else:
29+
name = VimSign.defined_signs[key]
30+
31+
self.show(name, buffer.number, line_number)
32+
pass
33+
34+
def define(self, sign_text, highlight_colour):
35+
""" Defines sign and highlight (if highlight_colour is not None). """
36+
sign_name = "sign%d" % VimSign.name_id
37+
if highlight_colour is None:
38+
vim.command("sign define %s text=%s" % (sign_name, sign_text))
39+
else:
40+
self.highlight_name = "highlight%d" % VimSign.name_id
41+
vim.command("highlight %s ctermbg=%s guibg=%s" % (self.highlight_name,
42+
highlight_colour,
43+
highlight_colour))
44+
vim.command("sign define %s text=%s linehl=%s texthl=%s" % (sign_name,
45+
sign_text,
46+
self.highlight_name,
47+
self.highlight_name))
48+
VimSign.defined_signs[(sign_text, highlight_colour)] = sign_name
49+
VimSign.name_id += 1
50+
return sign_name
51+
52+
53+
def show(self, name, buffer_number, line_number):
54+
self.id = VimSign.sign_id
55+
VimSign.sign_id += 1
56+
vim.command("sign place %d name=%s line=%d buffer=%s" % (self.id, name, line_number, buffer_number))
57+
pass
58+
59+
def hide(self):
60+
vim.command("sign unplace %d" % self.id)
61+
pass
62+
63+
class BreakpointSign(VimSign):
64+
def __init__(self, buffer, line_number, is_resolved):
65+
txt = VimSign.SIGN_TEXT_BREAKPOINT_RESOLVED if is_resolved else VimSign.SIGN_TEXT_BREAKPOINT_UNRESOLVED
66+
super(BreakpointSign, self).__init__(txt, buffer, line_number)
67+
68+
class PCSign(VimSign):
69+
def __init__(self, buffer, line_number, is_selected_thread):
70+
super(PCSign, self).__init__(VimSign.SIGN_TEXT_PC,
71+
buffer,
72+
line_number,
73+
VimSign.SIGN_HIGHLIGHT_COLOUR_PC if is_selected_thread else None)
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
2+
# LLDB UI state in the Vim user interface.
3+
4+
import os, re, sys
5+
import lldb
6+
import vim
7+
from vim_panes import *
8+
from vim_signs import *
9+
10+
def is_same_file(a, b):
11+
""" returns true if paths a and b are the same file """
12+
a = os.path.realpath(a)
13+
b = os.path.realpath(b)
14+
return a in b or b in a
15+
16+
class UI:
17+
def __init__(self):
18+
""" Declare UI state variables """
19+
20+
# Default panes to display
21+
self.defaultPanes = ['breakpoints', 'backtrace', 'locals', 'threads', 'registers', 'disassembly']
22+
23+
# map of tuples (filename, line) --> SBBreakpoint
24+
self.markedBreakpoints = {}
25+
26+
# Currently shown signs
27+
self.breakpointSigns = {}
28+
self.pcSigns = []
29+
30+
# Container for panes
31+
self.paneCol = PaneLayout()
32+
33+
# All possible LLDB panes
34+
self.backtracePane = BacktracePane(self.paneCol)
35+
self.threadPane = ThreadPane(self.paneCol)
36+
self.disassemblyPane = DisassemblyPane(self.paneCol)
37+
self.localsPane = LocalsPane(self.paneCol)
38+
self.registersPane = RegistersPane(self.paneCol)
39+
self.breakPane = BreakpointsPane(self.paneCol)
40+
41+
def activate(self):
42+
""" Activate UI: display default set of panes """
43+
self.paneCol.prepare(self.defaultPanes)
44+
45+
def get_user_buffers(self, filter_name=None):
46+
""" Returns a list of buffers that are not a part of the LLDB UI. That is, they
47+
are not contained in the PaneLayout object self.paneCol.
48+
"""
49+
ret = []
50+
for w in vim.windows:
51+
b = w.buffer
52+
if not self.paneCol.contains(b.name):
53+
if filter_name is None or filter_name in b.name:
54+
ret.append(b)
55+
return ret
56+
57+
def update_pc(self, process, buffers, goto_file):
58+
""" Place the PC sign on the PC location of each thread's selected frame """
59+
60+
def GetPCSourceLocation(thread):
61+
""" Returns a tuple (thread_index, file, line, column) that represents where
62+
the PC sign should be placed for a thread.
63+
"""
64+
65+
frame = thread.GetSelectedFrame()
66+
frame_num = frame.GetFrameID()
67+
le = frame.GetLineEntry()
68+
while not le.IsValid() and frame_num < thread.GetNumFrames():
69+
frame_num += 1
70+
le = thread.GetFrameAtIndex(frame_num).GetLineEntry()
71+
72+
if le.IsValid():
73+
path = os.path.join(le.GetFileSpec().GetDirectory(), le.GetFileSpec().GetFilename())
74+
return (thread.GetIndexID(), path, le.GetLine(), le.GetColumn())
75+
return None
76+
77+
78+
# Clear all existing PC signs
79+
del_list = []
80+
for sign in self.pcSigns:
81+
sign.hide()
82+
del_list.append(sign)
83+
for sign in del_list:
84+
self.pcSigns.remove(sign)
85+
del sign
86+
87+
# Select a user (non-lldb) window
88+
if not self.paneCol.selectWindow(False):
89+
# No user window found; avoid clobbering by splitting
90+
vim.command(":vsp")
91+
92+
# Show a PC marker for each thread
93+
for thread in process:
94+
loc = GetPCSourceLocation(thread)
95+
if not loc:
96+
# no valid source locations for PCs. hide all existing PC markers
97+
continue
98+
99+
buf = None
100+
(tid, fname, line, col) = loc
101+
buffers = self.get_user_buffers(fname)
102+
is_selected = thread.GetIndexID() == process.GetSelectedThread().GetIndexID()
103+
if len(buffers) == 1:
104+
buf = buffers[0]
105+
if buf != vim.current.buffer:
106+
# Vim has an open buffer to the required file: select it
107+
vim.command('execute ":%db"' % buf.number)
108+
elif is_selected and vim.current.buffer.name not in fname and os.path.exists(fname) and goto_file:
109+
# FIXME: If current buffer is modified, vim will complain when we try to switch away.
110+
# Find a way to detect if the current buffer is modified, and...warn instead?
111+
vim.command('execute ":e %s"' % fname)
112+
buf = vim.current.buffer
113+
elif len(buffers) > 1 and goto_file:
114+
#FIXME: multiple open buffers match PC location
115+
continue
116+
else:
117+
continue
118+
119+
self.pcSigns.append(PCSign(buf, line, is_selected))
120+
121+
if is_selected and goto_file:
122+
# if the selected file has a PC marker, move the cursor there too
123+
curname = vim.current.buffer.name
124+
if curname is not None and is_same_file(curname, fname):
125+
move_cursor(line, 0)
126+
elif move_cursor:
127+
print "FIXME: not sure where to move cursor because %s != %s " % (vim.current.buffer.name, fname)
128+
129+
def update_breakpoints(self, target, buffers):
130+
""" Decorates buffer with signs corresponding to breakpoints in target. """
131+
132+
def GetBreakpointLocations(bp):
133+
""" Returns a list of tuples (resolved, filename, line) where a breakpoint was resolved. """
134+
if not bp.IsValid():
135+
sys.stderr.write("breakpoint is invalid, no locations")
136+
return []
137+
138+
ret = []
139+
numLocs = bp.GetNumLocations()
140+
for i in range(numLocs):
141+
loc = bp.GetLocationAtIndex(i)
142+
desc = get_description(loc, lldb.eDescriptionLevelFull)
143+
match = re.search('at\ ([^:]+):([\d]+)', desc)
144+
try:
145+
lineNum = int(match.group(2).strip())
146+
ret.append((loc.IsResolved(), match.group(1), lineNum))
147+
except ValueError as e:
148+
sys.stderr.write("unable to parse breakpoint location line number: '%s'" % match.group(2))
149+
sys.stderr.write(str(e))
150+
151+
return ret
152+
153+
154+
if target is None or not target.IsValid():
155+
return
156+
157+
needed_bps = {}
158+
for bp_index in range(target.GetNumBreakpoints()):
159+
bp = target.GetBreakpointAtIndex(bp_index)
160+
locations = GetBreakpointLocations(bp)
161+
for (is_resolved, file, line) in GetBreakpointLocations(bp):
162+
for buf in buffers:
163+
if file in buf.name:
164+
needed_bps[(buf, line, is_resolved)] = bp
165+
166+
# Hide any signs that correspond with disabled breakpoints
167+
del_list = []
168+
for (b, l, r) in self.breakpointSigns:
169+
if (b, l, r) not in needed_bps:
170+
self.breakpointSigns[(b, l, r)].hide()
171+
del_list.append((b, l, r))
172+
for d in del_list:
173+
del self.breakpointSigns[d]
174+
175+
# Show any signs for new breakpoints
176+
for (b, l, r) in needed_bps:
177+
bp = needed_bps[(b, l, r)]
178+
if self.haveBreakpoint(b.name, l):
179+
self.markedBreakpoints[(b.name, l)].append(bp)
180+
else:
181+
self.markedBreakpoints[(b.name, l)] = [bp]
182+
183+
if (b, l, r) not in self.breakpointSigns:
184+
s = BreakpointSign(b, l, r)
185+
self.breakpointSigns[(b, l, r)] = s
186+
187+
def update(self, target, status, controller, goto_file=False):
188+
""" Updates debugger info panels and breakpoint/pc marks and prints
189+
status to the vim status line. If goto_file is True, the user's
190+
cursor is moved to the source PC location in the selected frame.
191+
"""
192+
193+
self.paneCol.update(target, controller)
194+
self.update_breakpoints(target, self.get_user_buffers())
195+
196+
if target is not None and target.IsValid():
197+
process = target.GetProcess()
198+
if process is not None and process.IsValid():
199+
self.update_pc(process, self.get_user_buffers, goto_file)
200+
201+
if status is not None and len(status) > 0:
202+
print status
203+
204+
def haveBreakpoint(self, file, line):
205+
""" Returns True if we have a breakpoint at file:line, False otherwise """
206+
return (file, line) in self.markedBreakpoints
207+
208+
def getBreakpoints(self, fname, line):
209+
""" Returns the LLDB SBBreakpoint object at fname:line """
210+
if self.haveBreakpoint(fname, line):
211+
return self.markedBreakpoints[(fname, line)]
212+
else:
213+
return None
214+
215+
def deleteBreakpoints(self, name, line):
216+
del self.markedBreakpoints[(name, line)]
217+
218+
def showWindow(self, name):
219+
""" Shows (un-hides) window pane specified by name """
220+
if not self.paneCol.havePane(name):
221+
sys.stderr.write("unknown window: %s" % name)
222+
return False
223+
self.paneCol.prepare([name])
224+
return True
225+
226+
def hideWindow(self, name):
227+
""" Hides window pane specified by name """
228+
if not self.paneCol.havePane(name):
229+
sys.stderr.write("unknown window: %s" % name)
230+
return False
231+
self.paneCol.hide([name])
232+
return True
233+
234+
global ui
235+
ui = UI()

0 commit comments

Comments
 (0)
This repository has been archived.