diff options
Diffstat (limited to 'utils')
39 files changed, 6097 insertions, 0 deletions
diff --git a/utils/git-svn/convert.py b/utils/git-svn/convert.py new file mode 100755 index 000000000000..c230a016b7f0 --- /dev/null +++ b/utils/git-svn/convert.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +""" +Convert the raw message sources from git patch emails to git-am friendly files. + +Usage: + +1. Mail.app -> Save As -> api.eml (Raw Message Source) +2. .../convert.py api.eml +3. git am [--signoff] < api.eml +4. git svn dcommit [--commit-url https://id@llvm.org/svn/llvm-project/lldb/trunk] +""" + +import os, re, sys +import StringIO + +def usage(problem_file=None): + if problem_file: + print "%s is not a file" % problem_file + print "Usage: convert.py raw-message-source [raw-message-source2 ...]" + sys.exit(0) + +def do_convert(file): + """Skip all preceding mail message headers until 'From: ' is encountered. + Then for each line ('From: ' header included), replace the dos style CRLF + end-of-line with unix style LF end-of-line. + """ + print "converting %s ..." % file + + with open(file, 'r') as f_in: + content = f_in.read() + + # The new content to be written back to the same file. + new_content = StringIO.StringIO() + + # Boolean flag controls whether to start printing lines. + from_header_seen = False + + # By default, splitlines() don't include line breaks. CRLF should be gone. + for line in content.splitlines(): + # Wait till we scan the 'From: ' header before start printing the lines. + if not from_header_seen: + if not line.startswith('From: '): + continue + else: + from_header_seen = True + + print >> new_content, line + + with open(file, 'w') as f_out: + f_out.write(new_content.getvalue()) + + print "done" + +def main(): + if len(sys.argv) == 1: + usage() + # Convert the raw message source one by one. + for file in sys.argv[1:]: + if not os.path.isfile(file): + usage(file) + do_convert(file) + +if __name__ == '__main__': + main() diff --git a/utils/lui/Readme b/utils/lui/Readme new file mode 100644 index 000000000000..7ba51ce81105 --- /dev/null +++ b/utils/lui/Readme @@ -0,0 +1,36 @@ + +LLDB (Terminal) User Interface +------------------------------ + +This directory contains the curses user interface for LLDB. To use it, ensure Python can find your lldb module. You may have to modify PYTHONPATH for that purpose: + +$ export PYTHONPATH=/path/to/lldb/module + +Then, run the lui.py. To load a core file: +$ ./lui.py --core core + +To create a target from an executable: +$ ./lui.py /bin/echo "hello world" + +To attach to a running process: +$ ./lui.py --attach <pid> + + +Known Issues +------------ +1. Resizing the terminal will most likely cause lui to crash. +2. Missing paging in command-window +3. Only minimal testing (on Ubuntu Linux x86_64) + +Missing Features +---------------- +- stdin/stdout/stderr windows +- memory window +- backtrace window +- threads window +- tab-completion +- syntax-highlighting (via pygments library) +- (local) variables window +- registers window +- disassembly window +- custom layout diff --git a/utils/lui/breakwin.py b/utils/lui/breakwin.py new file mode 100644 index 000000000000..734f5eddba42 --- /dev/null +++ b/utils/lui/breakwin.py @@ -0,0 +1,90 @@ +##===-- breakwin.py ------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import cui +import curses +import lldb, lldbutil +import re + +class BreakWin(cui.ListWin): + def __init__(self, driver, x, y, w, h): + super(BreakWin, self).__init__(x, y, w, h) + self.driver = driver + self.update() + self.showDetails = {} + + def handleEvent(self, event): + if isinstance(event, lldb.SBEvent): + if lldb.SBBreakpoint.EventIsBreakpointEvent(event): + self.update() + if isinstance(event, int): + if event == ord('d'): + self.deleteSelected() + if event == curses.ascii.NL or event == curses.ascii.SP: + self.toggleSelected() + elif event == curses.ascii.TAB: + if self.getSelected() != -1: + target = self.driver.getTarget() + if not target.IsValid(): + return + i = target.GetBreakpointAtIndex(self.getSelected()).id + self.showDetails[i] = not self.showDetails[i] + self.update() + super(BreakWin, self).handleEvent(event) + + def toggleSelected(self): + if self.getSelected() == -1: + return + target = self.driver.getTarget() + if not target.IsValid(): + return + bp = target.GetBreakpointAtIndex(self.getSelected()) + bp.SetEnabled(not bp.IsEnabled()) + + def deleteSelected(self): + if self.getSelected() == -1: + return + target = self.driver.getTarget() + if not target.IsValid(): + return + bp = target.GetBreakpointAtIndex(self.getSelected()) + target.BreakpointDelete(bp.id) + + def update(self): + target = self.driver.getTarget() + if not target.IsValid(): + self.win.erase() + self.win.noutrefresh() + return + selected = self.getSelected() + self.clearItems() + for i in range(0, target.GetNumBreakpoints()): + bp = target.GetBreakpointAtIndex(i) + if bp.IsInternal(): + continue + text = lldbutil.get_description(bp) + # FIXME: Use an API for this, not parsing the description. + match = re.search('SBBreakpoint: id = ([^,]+), (.*)', text) + try: + id = match.group(1) + desc = match.group(2).strip() + if bp.IsEnabled(): + text = '%s: %s' % (id, desc) + else: + text = '%s: (disabled) %s' % (id, desc) + except ValueError as e: + # bp unparsable + pass + + if self.showDetails.setdefault(bp.id, False): + for location in bp: + desc = lldbutil.get_description(location, lldb.eDescriptionLevelFull) + text += '\n ' + desc + self.addItem(text) + self.setSelected(selected) diff --git a/utils/lui/commandwin.py b/utils/lui/commandwin.py new file mode 100644 index 000000000000..2eb2082c6c80 --- /dev/null +++ b/utils/lui/commandwin.py @@ -0,0 +1,121 @@ +##===-- commandwin.py ----------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import cui +import curses +import lldb +from itertools import islice + +class History(object): + def __init__(self): + self.data = {} + self.pos = 0 + self.tempEntry = '' + + def previous(self, curr): + if self.pos == len(self.data): + self.tempEntry = curr + + if self.pos < 0: + return '' + if self.pos == 0: + self.pos -= 1 + return '' + if self.pos > 0: + self.pos -= 1 + return self.data[self.pos] + + def next(self): + if self.pos < len(self.data): + self.pos += 1 + + if self.pos < len(self.data): + return self.data[self.pos] + elif self.tempEntry != '': + return self.tempEntry + else: + return '' + + def add(self, c): + self.tempEntry = '' + self.pos = len(self.data) + if self.pos == 0 or self.data[self.pos-1] != c: + self.data[self.pos] = c + self.pos += 1 + +class CommandWin(cui.TitledWin): + def __init__(self, driver, x, y, w, h): + super(CommandWin, self).__init__(x, y, w, h, "Commands") + self.command = "" + self.data = "" + driver.setSize(w, h) + + self.win.scrollok(1) + + self.driver = driver + self.history = History() + + def enterCallback(content): + self.handleCommand(content) + def tabCompleteCallback(content): + self.data = content + matches = lldb.SBStringList() + commandinterpreter = self.getCommandInterpreter() + commandinterpreter.HandleCompletion(self.data, self.el.index, 0, -1, matches) + if matches.GetSize() == 2: + self.el.content += matches.GetStringAtIndex(0) + self.el.index = len(self.el.content) + self.el.draw() + else: + self.win.move(self.el.starty, self.el.startx) + self.win.scroll(1) + self.win.addstr("Available Completions:") + self.win.scroll(1) + for m in islice(matches, 1, None): + self.win.addstr(self.win.getyx()[0], 0, m) + self.win.scroll(1) + self.el.draw() + + self.startline = self.win.getmaxyx()[0]-2 + + self.el = cui.CursesEditLine(self.win, self.history, enterCallback, tabCompleteCallback) + self.el.prompt = self.driver.getPrompt() + self.el.showPrompt(self.startline, 0) + + def handleCommand(self, cmd): + # enter! + self.win.scroll(1) # TODO: scroll more for longer commands + if cmd == '': + cmd = self.history.previous('') + elif cmd in ('q', 'quit'): + self.driver.terminate() + return + + self.history.add(cmd) + ret = self.driver.handleCommand(cmd) + if ret.Succeeded(): + out = ret.GetOutput() + attr = curses.A_NORMAL + else: + out = ret.GetError() + attr = curses.color_pair(3) # red on black + self.win.addstr(self.startline, 0, out + '\n', attr) + self.win.scroll(1) + self.el.showPrompt(self.startline, 0) + + def handleEvent(self, event): + if isinstance(event, int): + if event == curses.ascii.EOT and self.el.content == '': + # When the command is empty, treat CTRL-D as EOF. + self.driver.terminate() + return + self.el.handleEvent(event) + + def getCommandInterpreter(self): + return self.driver.getCommandInterpreter() diff --git a/utils/lui/cui.py b/utils/lui/cui.py new file mode 100755 index 000000000000..e82f0abac2cc --- /dev/null +++ b/utils/lui/cui.py @@ -0,0 +1,320 @@ +##===-- cui.py -----------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import curses +import curses.ascii +import threading + +class CursesWin(object): + def __init__(self, x, y, w, h): + self.win = curses.newwin(h, w, y, x) + self.focus = False + + def setFocus(self, focus): + self.focus = focus + def getFocus(self): + return self.focus + def canFocus(self): + return True + + def handleEvent(self, event): + return + def draw(self): + return + +class TextWin(CursesWin): + def __init__(self, x, y, w): + super(TextWin, self).__init__(x, y, w, 1) + self.win.bkgd(curses.color_pair(1)) + self.text = '' + self.reverse = False + + def canFocus(self): + return False + + def draw(self): + w = self.win.getmaxyx()[1] + text = self.text + if len(text) > w: + #trunc_length = len(text) - w + text = text[-w+1:] + if self.reverse: + self.win.addstr(0, 0, text, curses.A_REVERSE) + else: + self.win.addstr(0, 0, text) + self.win.noutrefresh() + + def setReverse(self, reverse): + self.reverse = reverse + + def setText(self, text): + self.text = text + +class TitledWin(CursesWin): + def __init__(self, x, y, w, h, title): + super(TitledWin, self).__init__(x, y+1, w, h-1) + self.title = title + self.title_win = TextWin(x, y, w) + self.title_win.setText(title) + self.draw() + + def setTitle(self, title): + self.title_win.setText(title) + + def draw(self): + self.title_win.setReverse(self.getFocus()) + self.title_win.draw() + self.win.noutrefresh() + +class ListWin(CursesWin): + def __init__(self, x, y, w, h): + super(ListWin, self).__init__(x, y, w, h) + self.items = [] + self.selected = 0 + self.first_drawn = 0 + self.win.leaveok(True) + + def draw(self): + if len(self.items) == 0: + self.win.erase() + return + + h, w = self.win.getmaxyx() + + allLines = [] + firstSelected = -1 + lastSelected = -1 + for i, item in enumerate(self.items): + lines = self.items[i].split('\n') + lines = lines if lines[len(lines)-1] != '' else lines[:-1] + if len(lines) == 0: + lines = [''] + + if i == self.getSelected(): + firstSelected = len(allLines) + allLines.extend(lines) + if i == self.selected: + lastSelected = len(allLines) - 1 + + if firstSelected < self.first_drawn: + self.first_drawn = firstSelected + elif lastSelected >= self.first_drawn + h: + self.first_drawn = lastSelected - h + 1 + + self.win.erase() + + begin = self.first_drawn + end = begin + h + + y = 0 + for i, line in list(enumerate(allLines))[begin:end]: + attr = curses.A_NORMAL + if i >= firstSelected and i <= lastSelected: + attr = curses.A_REVERSE + line = '{0:{width}}'.format(line, width=w-1) + + # Ignore the error we get from drawing over the bottom-right char. + try: + self.win.addstr(y, 0, line[:w], attr) + except curses.error: + pass + y += 1 + self.win.noutrefresh() + + def getSelected(self): + if self.items: + return self.selected + return -1 + + def setSelected(self, selected): + self.selected = selected + if self.selected < 0: + self.selected = 0 + elif self.selected >= len(self.items): + self.selected = len(self.items) - 1 + + def handleEvent(self, event): + if isinstance(event, int): + if len(self.items) > 0: + if event == curses.KEY_UP: + self.setSelected(self.selected - 1) + if event == curses.KEY_DOWN: + self.setSelected(self.selected + 1) + if event == curses.ascii.NL: + self.handleSelect(self.selected) + + def addItem(self, item): + self.items.append(item) + + def clearItems(self): + self.items = [] + + def handleSelect(self, index): + return + +class InputHandler(threading.Thread): + def __init__(self, screen, queue): + super(InputHandler, self).__init__() + self.screen = screen + self.queue = queue + + def run(self): + while True: + c = self.screen.getch() + self.queue.put(c) + + +class CursesUI(object): + """ Responsible for updating the console UI with curses. """ + def __init__(self, screen, event_queue): + self.screen = screen + self.event_queue = event_queue + + curses.start_color() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) + curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) + self.screen.bkgd(curses.color_pair(1)) + self.screen.clear() + + self.input_handler = InputHandler(self.screen, self.event_queue) + self.input_handler.daemon = True + + self.focus = 0 + + self.screen.refresh() + + def focusNext(self): + self.wins[self.focus].setFocus(False) + old = self.focus + while True: + self.focus += 1 + if self.focus >= len(self.wins): + self.focus = 0 + if self.wins[self.focus].canFocus(): + break + self.wins[self.focus].setFocus(True) + + def handleEvent(self, event): + if isinstance(event, int): + if event == curses.KEY_F3: + self.focusNext() + + def eventLoop(self): + + self.input_handler.start() + self.wins[self.focus].setFocus(True) + + while True: + self.screen.noutrefresh() + + for i, win in enumerate(self.wins): + if i != self.focus: + win.draw() + # Draw the focused window last so that the cursor shows up. + if self.wins: + self.wins[self.focus].draw() + curses.doupdate() # redraw the physical screen + + event = self.event_queue.get() + + for win in self.wins: + if isinstance(event, int): + if win.getFocus() or not win.canFocus(): + win.handleEvent(event) + else: + win.handleEvent(event) + self.handleEvent(event) + +class CursesEditLine(object): + """ Embed an 'editline'-compatible prompt inside a CursesWin. """ + def __init__(self, win, history, enterCallback, tabCompleteCallback): + self.win = win + self.history = history + self.enterCallback = enterCallback + self.tabCompleteCallback = tabCompleteCallback + + self.prompt = '' + self.content = '' + self.index = 0 + self.startx = -1 + self.starty = -1 + + def draw(self, prompt=None): + if not prompt: + prompt = self.prompt + (h, w) = self.win.getmaxyx() + if (len(prompt) + len(self.content)) / w + self.starty >= h-1: + self.win.scroll(1) + self.starty -= 1 + if self.starty < 0: + raise RuntimeError('Input too long; aborting') + (y, x) = (self.starty, self.startx) + + self.win.move(y, x) + self.win.clrtobot() + self.win.addstr(y, x, prompt) + remain = self.content + self.win.addstr(remain[:w-len(prompt)]) + remain = remain[w-len(prompt):] + while remain != '': + y += 1 + self.win.addstr(y, 0, remain[:w]) + remain = remain[w:] + + length = self.index + len(prompt) + self.win.move(self.starty + length / w, length % w) + + def showPrompt(self, y, x, prompt=None): + self.content = '' + self.index = 0 + self.startx = x + self.starty = y + self.draw(prompt) + + def handleEvent(self, event): + if not isinstance(event, int): + return # not handled + key = event + + if self.startx == -1: + raise RuntimeError('Trying to handle input without prompt') + + if key == curses.ascii.NL: + self.enterCallback(self.content) + elif key == curses.ascii.TAB: + self.tabCompleteCallback(self.content) + elif curses.ascii.isprint(key): + self.content = self.content[:self.index] + chr(key) + self.content[self.index:] + self.index += 1 + elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS: + if self.index > 0: + self.index -= 1 + self.content = self.content[:self.index] + self.content[self.index+1:] + elif key == curses.KEY_DC or key == curses.ascii.DEL or key == curses.ascii.EOT: + self.content = self.content[:self.index] + self.content[self.index+1:] + elif key == curses.ascii.VT: # CTRL-K + self.content = self.content[:self.index] + elif key == curses.KEY_LEFT or key == curses.ascii.STX: # left or CTRL-B + if self.index > 0: + self.index -= 1 + elif key == curses.KEY_RIGHT or key == curses.ascii.ACK: # right or CTRL-F + if self.index < len(self.content): + self.index += 1 + elif key == curses.ascii.SOH: # CTRL-A + self.index = 0 + elif key == curses.ascii.ENQ: # CTRL-E + self.index = len(self.content) + elif key == curses.KEY_UP or key == curses.ascii.DLE: # up or CTRL-P + self.content = self.history.previous(self.content) + self.index = len(self.content) + elif key == curses.KEY_DOWN or key == curses.ascii.SO: # down or CTRL-N + self.content = self.history.next() + self.index = len(self.content) + self.draw() diff --git a/utils/lui/debuggerdriver.py b/utils/lui/debuggerdriver.py new file mode 100644 index 000000000000..f7b885107cc6 --- /dev/null +++ b/utils/lui/debuggerdriver.py @@ -0,0 +1,138 @@ +##===-- debuggerdriver.py ------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + + +import lldb +import lldbutil +import sys +from threading import Thread + +class DebuggerDriver(Thread): + """ Drives the debugger and responds to events. """ + def __init__(self, debugger, event_queue): + Thread.__init__(self) + self.event_queue = event_queue + # This is probably not great because it does not give liblldb a chance to clean up + self.daemon = True + self.initialize(debugger) + + def initialize(self, debugger): + self.done = False + self.debugger = debugger + self.listener = debugger.GetListener() + if not self.listener.IsValid(): + raise "Invalid listener" + + self.listener.StartListeningForEventClass(self.debugger, + lldb.SBTarget.GetBroadcasterClassName(), + lldb.SBTarget.eBroadcastBitBreakpointChanged + #| lldb.SBTarget.eBroadcastBitModuleLoaded + #| lldb.SBTarget.eBroadcastBitModuleUnloaded + | lldb.SBTarget.eBroadcastBitWatchpointChanged + #| lldb.SBTarget.eBroadcastBitSymbolLoaded + ) + + self.listener.StartListeningForEventClass(self.debugger, + lldb.SBThread.GetBroadcasterClassName(), + lldb.SBThread.eBroadcastBitStackChanged + # lldb.SBThread.eBroadcastBitBreakpointChanged + | lldb.SBThread.eBroadcastBitThreadSuspended + | lldb.SBThread.eBroadcastBitThreadResumed + | lldb.SBThread.eBroadcastBitSelectedFrameChanged + | lldb.SBThread.eBroadcastBitThreadSelected + ) + + self.listener.StartListeningForEventClass(self.debugger, + lldb.SBProcess.GetBroadcasterClassName(), + lldb.SBProcess.eBroadcastBitStateChanged + | lldb.SBProcess.eBroadcastBitInterrupt + | lldb.SBProcess.eBroadcastBitSTDOUT + | lldb.SBProcess.eBroadcastBitSTDERR + | lldb.SBProcess.eBroadcastBitProfileData + ) + self.listener.StartListeningForEventClass(self.debugger, + lldb.SBCommandInterpreter.GetBroadcasterClass(), + lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit + | lldb.SBCommandInterpreter.eBroadcastBitResetPrompt + | lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived + | lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData + | lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData + ) + def createTarget(self, target_image, args=None): + self.handleCommand("target create %s" % target_image) + if args is not None: + self.handleCommand("settings set target.run-args %s" % args) + + def attachProcess(self, pid): + self.handleCommand("process attach -p %d" % pid) + pass + + def loadCore(self, corefile): + self.handleCommand("target create -c %s" % corefile) + pass + + def setDone(self): + self.done = True + + def isDone(self): + return self.done + + def getPrompt(self): + return self.debugger.GetPrompt() + + def getCommandInterpreter(self): + return self.debugger.GetCommandInterpreter() + + def getSourceManager(self): + return self.debugger.GetSourceManager() + + def setSize(self, width, height): + # FIXME: respect height + self.debugger.SetTerminalWidth(width) + + def getTarget(self): + return self.debugger.GetTargetAtIndex(0) + + def handleCommand(self, cmd): + ret = lldb.SBCommandReturnObject() + self.getCommandInterpreter().HandleCommand(cmd, ret) + return ret + + def eventLoop(self): + while not self.isDone(): + event = lldb.SBEvent() + got_event = self.listener.WaitForEvent(lldb.UINT32_MAX, event) + if got_event and not event.IsValid(): + self.winAddStr("Warning: Invalid or no event...") + continue + elif not event.GetBroadcaster().IsValid(): + continue + + self.event_queue.put(event) + + def run(self): + self.eventLoop() + + def terminate(self): + lldb.SBDebugger.Terminate() + sys.exit(0) + +def createDriver(debugger, event_queue): + driver = DebuggerDriver(debugger, event_queue) + #driver.start() + # if pid specified: + # - attach to pid + # else if core file specified + # - create target from corefile + # else + # - create target from file + # - settings append target.run-args <args-from-cmdline> + # source .lldbinit file + + return driver diff --git a/utils/lui/eventwin.py b/utils/lui/eventwin.py new file mode 100644 index 000000000000..327978a3b456 --- /dev/null +++ b/utils/lui/eventwin.py @@ -0,0 +1,25 @@ +##===-- eventwin.py ------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import cui +import lldb, lldbutil + +class EventWin(cui.TitledWin): + def __init__(self, x, y, w, h): + super(EventWin, self).__init__(x, y, w, h, 'LLDB Event Log') + self.win.scrollok(1) + super(EventWin, self).draw() + + def handleEvent(self, event): + if isinstance(event, lldb.SBEvent): + self.win.scroll() + h = self.win.getmaxyx()[0] + self.win.addstr(h-1, 0, lldbutil.get_description(event)) + return + diff --git a/utils/lui/lldbutil.py b/utils/lui/lldbutil.py new file mode 100644 index 000000000000..8bdb074728f3 --- /dev/null +++ b/utils/lui/lldbutil.py @@ -0,0 +1,899 @@ +##===-- lldbutil.py ------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +""" +This LLDB module contains miscellaneous utilities. +Some of the test suite takes advantage of the utility functions defined here. +They can also be useful for general purpose lldb scripting. +""" + +import lldb +import os, sys +import StringIO + +# =================================================== +# Utilities for locating/checking executable programs +# =================================================== + +def is_exe(fpath): + """Returns True if fpath is an executable.""" + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def which(program): + """Returns the full path to a program; None otherwise.""" + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +# =================================================== +# Disassembly for an SBFunction or an SBSymbol object +# =================================================== + +def disassemble(target, function_or_symbol): + """Disassemble the function or symbol given a target. + + It returns the disassembly content in a string object. + """ + buf = StringIO.StringIO() + insts = function_or_symbol.GetInstructions(target) + for i in insts: + print >> buf, i + return buf.getvalue() + +# ========================================================== +# Integer (byte size 1, 2, 4, and 8) to bytearray conversion +# ========================================================== + +def int_to_bytearray(val, bytesize): + """Utility function to convert an integer into a bytearray. + + It returns the bytearray in the little endian format. It is easy to get the + big endian format, just do ba.reverse() on the returned object. + """ + import struct + + if bytesize == 1: + return bytearray([val]) + + # Little endian followed by a format character. + template = "<%c" + if bytesize == 2: + fmt = template % 'h' + elif bytesize == 4: + fmt = template % 'i' + elif bytesize == 4: + fmt = template % 'q' + else: + return None + + packed = struct.pack(fmt, val) + return bytearray(map(ord, packed)) + +def bytearray_to_int(bytes, bytesize): + """Utility function to convert a bytearray into an integer. + + It interprets the bytearray in the little endian format. For a big endian + bytearray, just do ba.reverse() on the object before passing it in. + """ + import struct + + if bytesize == 1: + return bytes[0] + + # Little endian followed by a format character. + template = "<%c" + if bytesize == 2: + fmt = template % 'h' + elif bytesize == 4: + fmt = template % 'i' + elif bytesize == 4: + fmt = template % 'q' + else: + return None + + unpacked = struct.unpack(fmt, str(bytes)) + return unpacked[0] + + +# ============================================================== +# Get the description of an lldb object or None if not available +# ============================================================== +def get_description(obj, option=None): + """Calls lldb_obj.GetDescription() and returns a string, or None. + + For SBTarget, SBBreakpointLocation, and SBWatchpoint lldb objects, an extra + option can be passed in to describe the detailed level of description + desired: + o lldb.eDescriptionLevelBrief + o lldb.eDescriptionLevelFull + o lldb.eDescriptionLevelVerbose + """ + method = getattr(obj, 'GetDescription') + if not method: + return None + tuple = (lldb.SBTarget, lldb.SBBreakpointLocation, lldb.SBWatchpoint) + if isinstance(obj, tuple): + if option is None: + option = lldb.eDescriptionLevelBrief + + stream = lldb.SBStream() + if option is None: + success = method(stream) + else: + success = method(stream, option) + if not success: + return None + return stream.GetData() + + +# ================================================= +# Convert some enum value to its string counterpart +# ================================================= + +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") + +def stop_reason_to_str(enum): + """Returns the stopReason string given an enum.""" + if enum == lldb.eStopReasonInvalid: + return "invalid" + elif enum == lldb.eStopReasonNone: + return "none" + elif enum == lldb.eStopReasonTrace: + return "trace" + elif enum == lldb.eStopReasonBreakpoint: + return "breakpoint" + elif enum == lldb.eStopReasonWatchpoint: + return "watchpoint" + elif enum == lldb.eStopReasonSignal: + return "signal" + elif enum == lldb.eStopReasonException: + return "exception" + elif enum == lldb.eStopReasonPlanComplete: + return "plancomplete" + elif enum == lldb.eStopReasonThreadExiting: + return "threadexiting" + else: + raise Exception("Unknown StopReason enum") + +def symbol_type_to_str(enum): + """Returns the symbolType string given an enum.""" + if enum == lldb.eSymbolTypeInvalid: + return "invalid" + elif enum == lldb.eSymbolTypeAbsolute: + return "absolute" + elif enum == lldb.eSymbolTypeCode: + return "code" + elif enum == lldb.eSymbolTypeData: + return "data" + elif enum == lldb.eSymbolTypeTrampoline: + return "trampoline" + elif enum == lldb.eSymbolTypeRuntime: + return "runtime" + elif enum == lldb.eSymbolTypeException: + return "exception" + elif enum == lldb.eSymbolTypeSourceFile: + return "sourcefile" + elif enum == lldb.eSymbolTypeHeaderFile: + return "headerfile" + elif enum == lldb.eSymbolTypeObjectFile: + return "objectfile" + elif enum == lldb.eSymbolTypeCommonBlock: + return "commonblock" + elif enum == lldb.eSymbolTypeBlock: + return "block" + elif enum == lldb.eSymbolTypeLocal: + return "local" + elif enum == lldb.eSymbolTypeParam: + return "param" + elif enum == lldb.eSymbolTypeVariable: + return "variable" + elif enum == lldb.eSymbolTypeVariableType: + return "variabletype" + elif enum == lldb.eSymbolTypeLineEntry: + return "lineentry" + elif enum == lldb.eSymbolTypeLineHeader: + return "lineheader" + elif enum == lldb.eSymbolTypeScopeBegin: + return "scopebegin" + elif enum == lldb.eSymbolTypeScopeEnd: + return "scopeend" + elif enum == lldb.eSymbolTypeAdditional: + return "additional" + elif enum == lldb.eSymbolTypeCompiler: + return "compiler" + elif enum == lldb.eSymbolTypeInstrumentation: + return "instrumentation" + elif enum == lldb.eSymbolTypeUndefined: + return "undefined" + +def value_type_to_str(enum): + """Returns the valueType string given an enum.""" + if enum == lldb.eValueTypeInvalid: + return "invalid" + elif enum == lldb.eValueTypeVariableGlobal: + return "global_variable" + elif enum == lldb.eValueTypeVariableStatic: + return "static_variable" + elif enum == lldb.eValueTypeVariableArgument: + return "argument_variable" + elif enum == lldb.eValueTypeVariableLocal: + return "local_variable" + elif enum == lldb.eValueTypeRegister: + return "register" + elif enum == lldb.eValueTypeRegisterSet: + return "register_set" + elif enum == lldb.eValueTypeConstResult: + return "constant_result" + else: + raise Exception("Unknown ValueType enum") + + +# ================================================== +# Get stopped threads due to each stop reason. +# ================================================== + +def sort_stopped_threads(process, + breakpoint_threads = None, + crashed_threads = None, + watchpoint_threads = None, + signal_threads = None, + exiting_threads = None, + other_threads = None): + """ Fills array *_threads with threads stopped for the corresponding stop + reason. + """ + for lst in [breakpoint_threads, + watchpoint_threads, + signal_threads, + exiting_threads, + other_threads]: + if lst is not None: + lst[:] = [] + + for thread in process: + dispatched = False + for (reason, list) in [(lldb.eStopReasonBreakpoint, breakpoint_threads), + (lldb.eStopReasonException, crashed_threads), + (lldb.eStopReasonWatchpoint, watchpoint_threads), + (lldb.eStopReasonSignal, signal_threads), + (lldb.eStopReasonThreadExiting, exiting_threads), + (None, other_threads)]: + if not dispatched and list is not None: + if thread.GetStopReason() == reason or reason is None: + list.append(thread) + dispatched = True + +# ================================================== +# Utility functions for setting breakpoints +# ================================================== + +def run_break_set_by_file_and_line (test, file_name, line_number, extra_options = None, num_expected_locations = 1, loc_exact=False, module_name=None): + """Set a breakpoint by file and line, returning the breakpoint number. + + If extra_options is not None, then we append it to the breakpoint set command. + + If num_expected_locations is -1 we check that we got AT LEAST one location, otherwise we check that num_expected_locations equals the number of locations. + + If loc_exact is true, we check that there is one location, and that location must be at the input file and line number.""" + + if file_name == None: + command = 'breakpoint set -l %d'%(line_number) + else: + command = 'breakpoint set -f "%s" -l %d'%(file_name, line_number) + + if module_name: + command += " --shlib '%s'" % (module_name) + + if extra_options: + command += " " + extra_options + + break_results = run_break_set_command (test, command) + + if num_expected_locations == 1 and loc_exact: + check_breakpoint_result (test, break_results, num_locations=num_expected_locations, file_name = file_name, line_number = line_number, module_name=module_name) + else: + check_breakpoint_result (test, break_results, num_locations = num_expected_locations) + + return get_bpno_from_match (break_results) + +def run_break_set_by_symbol (test, symbol, extra_options = None, num_expected_locations = -1, sym_exact = False, module_name=None): + """Set a breakpoint by symbol name. Common options are the same as run_break_set_by_file_and_line. + + If sym_exact is true, then the output symbol must match the input exactly, otherwise we do a substring match.""" + command = 'breakpoint set -n "%s"'%(symbol) + + if module_name: + command += " --shlib '%s'" % (module_name) + + if extra_options: + command += " " + extra_options + + break_results = run_break_set_command (test, command) + + if num_expected_locations == 1 and sym_exact: + check_breakpoint_result (test, break_results, num_locations = num_expected_locations, symbol_name = symbol, module_name=module_name) + else: + check_breakpoint_result (test, break_results, num_locations = num_expected_locations) + + return get_bpno_from_match (break_results) + +def run_break_set_by_selector (test, selector, extra_options = None, num_expected_locations = -1, module_name=None): + """Set a breakpoint by selector. Common options are the same as run_break_set_by_file_and_line.""" + + command = 'breakpoint set -S "%s"' % (selector) + + if module_name: + command += ' --shlib "%s"' % (module_name) + + if extra_options: + command += " " + extra_options + + break_results = run_break_set_command (test, command) + + if num_expected_locations == 1: + check_breakpoint_result (test, break_results, num_locations = num_expected_locations, symbol_name = selector, symbol_match_exact=False, module_name=module_name) + else: + check_breakpoint_result (test, break_results, num_locations = num_expected_locations) + + return get_bpno_from_match (break_results) + +def run_break_set_by_regexp (test, regexp, extra_options=None, num_expected_locations=-1): + """Set a breakpoint by regular expression match on symbol name. Common options are the same as run_break_set_by_file_and_line.""" + + command = 'breakpoint set -r "%s"'%(regexp) + if extra_options: + command += " " + extra_options + + break_results = run_break_set_command (test, command) + + check_breakpoint_result (test, break_results, num_locations=num_expected_locations) + + return get_bpno_from_match (break_results) + +def run_break_set_by_source_regexp (test, regexp, extra_options=None, num_expected_locations=-1): + """Set a breakpoint by source regular expression. Common options are the same as run_break_set_by_file_and_line.""" + command = 'breakpoint set -p "%s"'%(regexp) + if extra_options: + command += " " + extra_options + + break_results = run_break_set_command (test, command) + + check_breakpoint_result (test, break_results, num_locations=num_expected_locations) + + return get_bpno_from_match (break_results) + +def run_break_set_command (test, command): + """Run the command passed in - it must be some break set variant - and analyze the result. + Returns a dictionary of information gleaned from the command-line results. + Will assert if the breakpoint setting fails altogether. + + Dictionary will contain: + bpno - breakpoint of the newly created breakpoint, -1 on error. + num_locations - number of locations set for the breakpoint. + + If there is only one location, the dictionary MAY contain: + file - source file name + line_no - source line number + symbol - symbol name + inline_symbol - inlined symbol name + offset - offset from the original symbol + module - module + address - address at which the breakpoint was set.""" + + patterns = [r"^Breakpoint (?P<bpno>[0-9]+): (?P<num_locations>[0-9]+) locations\.$", + r"^Breakpoint (?P<bpno>[0-9]+): (?P<num_locations>no) locations \(pending\)\.", + r"^Breakpoint (?P<bpno>[0-9]+): where = (?P<module>.*)`(?P<symbol>[+\-]{0,1}[^+]+)( \+ (?P<offset>[0-9]+)){0,1}( \[inlined\] (?P<inline_symbol>.*)){0,1} at (?P<file>[^:]+):(?P<line_no>[0-9]+), address = (?P<address>0x[0-9a-fA-F]+)$", + r"^Breakpoint (?P<bpno>[0-9]+): where = (?P<module>.*)`(?P<symbol>.*)( \+ (?P<offset>[0-9]+)){0,1}, address = (?P<address>0x[0-9a-fA-F]+)$"] + match_object = test.match (command, patterns) + break_results = match_object.groupdict() + + # We always insert the breakpoint number, setting it to -1 if we couldn't find it + # Also, make sure it gets stored as an integer. + if not 'bpno' in break_results: + break_results['bpno'] = -1 + else: + break_results['bpno'] = int(break_results['bpno']) + + # We always insert the number of locations + # If ONE location is set for the breakpoint, then the output doesn't mention locations, but it has to be 1... + # We also make sure it is an integer. + + if not 'num_locations' in break_results: + num_locations = 1 + else: + num_locations = break_results['num_locations'] + if num_locations == 'no': + num_locations = 0 + else: + num_locations = int(break_results['num_locations']) + + break_results['num_locations'] = num_locations + + if 'line_no' in break_results: + break_results['line_no'] = int(break_results['line_no']) + + return break_results + +def get_bpno_from_match (break_results): + return int (break_results['bpno']) + +def check_breakpoint_result (test, break_results, file_name=None, line_number=-1, symbol_name=None, symbol_match_exact=True, module_name=None, offset=-1, num_locations=-1): + + out_num_locations = break_results['num_locations'] + + if num_locations == -1: + test.assertTrue (out_num_locations > 0, "Expecting one or more locations, got none.") + else: + test.assertTrue (num_locations == out_num_locations, "Expecting %d locations, got %d."%(num_locations, out_num_locations)) + + if file_name: + out_file_name = "" + if 'file' in break_results: + out_file_name = break_results['file'] + test.assertTrue (file_name == out_file_name, "Breakpoint file name '%s' doesn't match resultant name '%s'."%(file_name, out_file_name)) + + if line_number != -1: + out_file_line = -1 + if 'line_no' in break_results: + out_line_number = break_results['line_no'] + + test.assertTrue (line_number == out_line_number, "Breakpoint line number %s doesn't match resultant line %s."%(line_number, out_line_number)) + + if symbol_name: + out_symbol_name = "" + # Look first for the inlined symbol name, otherwise use the symbol name: + if 'inline_symbol' in break_results and break_results['inline_symbol']: + out_symbol_name = break_results['inline_symbol'] + elif 'symbol' in break_results: + out_symbol_name = break_results['symbol'] + + if symbol_match_exact: + test.assertTrue(symbol_name == out_symbol_name, "Symbol name '%s' doesn't match resultant symbol '%s'."%(symbol_name, out_symbol_name)) + else: + test.assertTrue(out_symbol_name.find(symbol_name) != -1, "Symbol name '%s' isn't in resultant symbol '%s'."%(symbol_name, out_symbol_name)) + + if module_name: + out_nodule_name = None + if 'module' in break_results: + out_module_name = break_results['module'] + + test.assertTrue (module_name.find(out_module_name) != -1, "Symbol module name '%s' isn't in expected module name '%s'."%(out_module_name, module_name)) + +# ================================================== +# Utility functions related to Threads and Processes +# ================================================== + +def get_stopped_threads(process, reason): + """Returns the thread(s) with the specified stop reason in a list. + + The list can be empty if no such thread exists. + """ + threads = [] + for t in process: + if t.GetStopReason() == reason: + threads.append(t) + return threads + +def get_stopped_thread(process, reason): + """A convenience function which returns the first thread with the given stop + reason or None. + + Example usages: + + 1. Get the stopped thread due to a breakpoint condition + + ... + from lldbutil import get_stopped_thread + thread = get_stopped_thread(process, lldb.eStopReasonPlanComplete) + self.assertTrue(thread.IsValid(), "There should be a thread stopped due to breakpoint condition") + ... + + 2. Get the thread stopped due to a breakpoint + + ... + from lldbutil import get_stopped_thread + thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) + self.assertTrue(thread.IsValid(), "There should be a thread stopped due to breakpoint") + ... + + """ + threads = get_stopped_threads(process, reason) + if len(threads) == 0: + return None + return threads[0] + +def get_threads_stopped_at_breakpoint (process, bkpt): + """ For a stopped process returns the thread stopped at the breakpoint passed in bkpt""" + stopped_threads = [] + threads = [] + + stopped_threads = get_stopped_threads (process, lldb.eStopReasonBreakpoint) + + if len(stopped_threads) == 0: + return threads + + for thread in stopped_threads: + # Make sure we've hit our breakpoint... + break_id = thread.GetStopReasonDataAtIndex (0) + if break_id == bkpt.GetID(): + threads.append(thread) + + return threads + +def continue_to_breakpoint (process, bkpt): + """ Continues the process, if it stops, returns the threads stopped at bkpt; otherwise, returns None""" + process.Continue() + if process.GetState() != lldb.eStateStopped: + return None + else: + return get_threads_stopped_at_breakpoint (process, bkpt) + +def get_caller_symbol(thread): + """ + Returns the symbol name for the call site of the leaf function. + """ + depth = thread.GetNumFrames() + if depth <= 1: + return None + caller = thread.GetFrameAtIndex(1).GetSymbol() + if caller: + return caller.GetName() + else: + return None + + +def get_function_names(thread): + """ + Returns a sequence of function names from the stack frames of this thread. + """ + def GetFuncName(i): + return thread.GetFrameAtIndex(i).GetFunctionName() + + return map(GetFuncName, range(thread.GetNumFrames())) + + +def get_symbol_names(thread): + """ + Returns a sequence of symbols for this thread. + """ + def GetSymbol(i): + return thread.GetFrameAtIndex(i).GetSymbol().GetName() + + return map(GetSymbol, range(thread.GetNumFrames())) + + +def get_pc_addresses(thread): + """ + Returns a sequence of pc addresses for this thread. + """ + def GetPCAddress(i): + return thread.GetFrameAtIndex(i).GetPCAddress() + + return map(GetPCAddress, range(thread.GetNumFrames())) + + +def get_filenames(thread): + """ + Returns a sequence of file names from the stack frames of this thread. + """ + def GetFilename(i): + return thread.GetFrameAtIndex(i).GetLineEntry().GetFileSpec().GetFilename() + + return map(GetFilename, range(thread.GetNumFrames())) + + +def get_line_numbers(thread): + """ + Returns a sequence of line numbers from the stack frames of this thread. + """ + def GetLineNumber(i): + return thread.GetFrameAtIndex(i).GetLineEntry().GetLine() + + return map(GetLineNumber, range(thread.GetNumFrames())) + + +def get_module_names(thread): + """ + Returns a sequence of module names from the stack frames of this thread. + """ + def GetModuleName(i): + return thread.GetFrameAtIndex(i).GetModule().GetFileSpec().GetFilename() + + return map(GetModuleName, range(thread.GetNumFrames())) + + +def get_stack_frames(thread): + """ + Returns a sequence of stack frames for this thread. + """ + def GetStackFrame(i): + return thread.GetFrameAtIndex(i) + + return map(GetStackFrame, range(thread.GetNumFrames())) + + +def print_stacktrace(thread, string_buffer = False): + """Prints a simple stack trace of this thread.""" + + output = StringIO.StringIO() if string_buffer else sys.stdout + target = thread.GetProcess().GetTarget() + + depth = thread.GetNumFrames() + + mods = get_module_names(thread) + funcs = get_function_names(thread) + symbols = get_symbol_names(thread) + files = get_filenames(thread) + lines = get_line_numbers(thread) + addrs = get_pc_addresses(thread) + + if thread.GetStopReason() != lldb.eStopReasonInvalid: + desc = "stop reason=" + stop_reason_to_str(thread.GetStopReason()) + else: + desc = "" + print >> output, "Stack trace for thread id={0:#x} name={1} queue={2} ".format( + thread.GetThreadID(), thread.GetName(), thread.GetQueueName()) + desc + + for i in range(depth): + frame = thread.GetFrameAtIndex(i) + function = frame.GetFunction() + + load_addr = addrs[i].GetLoadAddress(target) + if not function: + file_addr = addrs[i].GetFileAddress() + start_addr = frame.GetSymbol().GetStartAddress().GetFileAddress() + symbol_offset = file_addr - start_addr + print >> output, " frame #{num}: {addr:#016x} {mod}`{symbol} + {offset}".format( + num=i, addr=load_addr, mod=mods[i], symbol=symbols[i], offset=symbol_offset) + else: + print >> output, " frame #{num}: {addr:#016x} {mod}`{func} at {file}:{line} {args}".format( + num=i, addr=load_addr, mod=mods[i], + func='%s [inlined]' % funcs[i] if frame.IsInlined() else funcs[i], + file=files[i], line=lines[i], + args=get_args_as_string(frame, showFuncName=False) if not frame.IsInlined() else '()') + + if string_buffer: + return output.getvalue() + + +def print_stacktraces(process, string_buffer = False): + """Prints the stack traces of all the threads.""" + + output = StringIO.StringIO() if string_buffer else sys.stdout + + print >> output, "Stack traces for " + str(process) + + for thread in process: + print >> output, print_stacktrace(thread, string_buffer=True) + + if string_buffer: + return output.getvalue() + +# =================================== +# Utility functions related to Frames +# =================================== + +def get_parent_frame(frame): + """ + Returns the parent frame of the input frame object; None if not available. + """ + thread = frame.GetThread() + parent_found = False + for f in thread: + if parent_found: + return f + if f.GetFrameID() == frame.GetFrameID(): + parent_found = True + + # If we reach here, no parent has been found, return None. + return None + +def get_args_as_string(frame, showFuncName=True): + """ + Returns the args of the input frame object as a string. + """ + # arguments => True + # locals => False + # statics => False + # in_scope_only => True + vars = frame.GetVariables(True, False, False, True) # type of SBValueList + args = [] # list of strings + for var in vars: + args.append("(%s)%s=%s" % (var.GetTypeName(), + var.GetName(), + var.GetValue())) + if frame.GetFunction(): + name = frame.GetFunction().GetName() + elif frame.GetSymbol(): + name = frame.GetSymbol().GetName() + else: + name = "" + if showFuncName: + return "%s(%s)" % (name, ", ".join(args)) + else: + return "(%s)" % (", ".join(args)) + +def print_registers(frame, string_buffer = False): + """Prints all the register sets of the frame.""" + + output = StringIO.StringIO() if string_buffer else sys.stdout + + print >> output, "Register sets for " + str(frame) + + registerSet = frame.GetRegisters() # Return type of SBValueList. + print >> output, "Frame registers (size of register set = %d):" % registerSet.GetSize() + for value in registerSet: + #print >> output, value + print >> output, "%s (number of children = %d):" % (value.GetName(), value.GetNumChildren()) + for child in value: + print >> output, "Name: %s, Value: %s" % (child.GetName(), child.GetValue()) + + if string_buffer: + return output.getvalue() + +def get_registers(frame, kind): + """Returns the registers given the frame and the kind of registers desired. + + Returns None if there's no such kind. + """ + registerSet = frame.GetRegisters() # Return type of SBValueList. + for value in registerSet: + if kind.lower() in value.GetName().lower(): + return value + + return None + +def get_GPRs(frame): + """Returns the general purpose registers of the frame as an SBValue. + + The returned SBValue object is iterable. An example: + ... + from lldbutil import get_GPRs + regs = get_GPRs(frame) + for reg in regs: + print "%s => %s" % (reg.GetName(), reg.GetValue()) + ... + """ + return get_registers(frame, "general purpose") + +def get_FPRs(frame): + """Returns the floating point registers of the frame as an SBValue. + + The returned SBValue object is iterable. An example: + ... + from lldbutil import get_FPRs + regs = get_FPRs(frame) + for reg in regs: + print "%s => %s" % (reg.GetName(), reg.GetValue()) + ... + """ + return get_registers(frame, "floating point") + +def get_ESRs(frame): + """Returns the exception state registers of the frame as an SBValue. + + The returned SBValue object is iterable. An example: + ... + from lldbutil import get_ESRs + regs = get_ESRs(frame) + for reg in regs: + print "%s => %s" % (reg.GetName(), reg.GetValue()) + ... + """ + return get_registers(frame, "exception state") + +# ====================================== +# Utility classes/functions for SBValues +# ====================================== + +class BasicFormatter(object): + """The basic formatter inspects the value object and prints the value.""" + def format(self, value, buffer=None, indent=0): + if not buffer: + output = StringIO.StringIO() + else: + output = buffer + # If there is a summary, it suffices. + val = value.GetSummary() + # Otherwise, get the value. + if val == None: + val = value.GetValue() + if val == None and value.GetNumChildren() > 0: + val = "%s (location)" % value.GetLocation() + print >> output, "{indentation}({type}) {name} = {value}".format( + indentation = ' ' * indent, + type = value.GetTypeName(), + name = value.GetName(), + value = val) + return output.getvalue() + +class ChildVisitingFormatter(BasicFormatter): + """The child visiting formatter prints the value and its immediate children. + + The constructor takes a keyword arg: indent_child, which defaults to 2. + """ + def __init__(self, indent_child=2): + """Default indentation of 2 SPC's for the children.""" + self.cindent = indent_child + def format(self, value, buffer=None): + if not buffer: + output = StringIO.StringIO() + else: + output = buffer + + BasicFormatter.format(self, value, buffer=output) + for child in value: + BasicFormatter.format(self, child, buffer=output, indent=self.cindent) + + return output.getvalue() + +class RecursiveDecentFormatter(BasicFormatter): + """The recursive decent formatter prints the value and the decendents. + + The constructor takes two keyword args: indent_level, which defaults to 0, + and indent_child, which defaults to 2. The current indentation level is + determined by indent_level, while the immediate children has an additional + indentation by inden_child. + """ + def __init__(self, indent_level=0, indent_child=2): + self.lindent = indent_level + self.cindent = indent_child + def format(self, value, buffer=None): + if not buffer: + output = StringIO.StringIO() + else: + output = buffer + + BasicFormatter.format(self, value, buffer=output, indent=self.lindent) + new_indent = self.lindent + self.cindent + for child in value: + if child.GetSummary() != None: + BasicFormatter.format(self, child, buffer=output, indent=new_indent) + else: + if child.GetNumChildren() > 0: + rdf = RecursiveDecentFormatter(indent_level=new_indent) + rdf.format(child, buffer=output) + else: + BasicFormatter.format(self, child, buffer=output, indent=new_indent) + + return output.getvalue() diff --git a/utils/lui/lui.py b/utils/lui/lui.py new file mode 100755 index 000000000000..31b152c77f0d --- /dev/null +++ b/utils/lui/lui.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +##===-- lui.py -----------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + + + +import curses + +import lldb +import lldbutil + +from optparse import OptionParser +import os +import signal +import sys + +import Queue + +import debuggerdriver +import cui + +import breakwin +import commandwin +import eventwin +import sourcewin +import statuswin + +event_queue = None + +def handle_args(driver, argv): + parser = OptionParser() + parser.add_option("-p", "--attach", dest="pid", help="Attach to specified Process ID", type="int") + parser.add_option("-c", "--core", dest="core", help="Load specified core file", type="string") + + (options, args) = parser.parse_args(argv) + + if options.pid is not None: + try: + pid = int(options.pid) + driver.attachProcess(ui, pid) + except ValueError: + print "Error: expecting integer PID, got '%s'" % options.pid + elif options.core is not None: + if not os.path.exists(options.core): + raise Exception("Specified core file '%s' does not exist." % options.core) + driver.loadCore(options.core) + elif len(args) == 2: + if not os.path.isfile(args[1]): + raise Exception("Specified target '%s' does not exist" % args[1]) + driver.createTarget(args[1]) + elif len(args) > 2: + if not os.path.isfile(args[1]): + raise Exception("Specified target '%s' does not exist" % args[1]) + driver.createTarget(args[1], args[2:]) + +def sigint_handler(signal, frame): + global debugger + debugger.terminate() + +class LLDBUI(cui.CursesUI): + def __init__(self, screen, event_queue, driver): + super(LLDBUI, self).__init__(screen, event_queue) + + self.driver = driver + + h, w = self.screen.getmaxyx() + + command_win_height = 20 + break_win_width = 60 + + self.status_win = statuswin.StatusWin(0, h-1, w, 1) + h -= 1 + self.command_win = commandwin.CommandWin(driver, 0, h - command_win_height, + w, command_win_height) + h -= command_win_height + self.source_win = sourcewin.SourceWin(driver, 0, 0, + w - break_win_width - 1, h) + self.break_win = breakwin.BreakWin(driver, w - break_win_width, 0, + break_win_width, h) + + + self.wins = [self.status_win, + #self.event_win, + self.source_win, + self.break_win, + self.command_win, + ] + + self.focus = len(self.wins) - 1 # index of command window; + + def handleEvent(self, event): + # hack + if isinstance(event, int): + if event == curses.KEY_F10: + self.driver.terminate() + if event == 20: # ctrl-T + def foo(cmd): + ret = lldb.SBCommandReturnObject() + self.driver.getCommandInterpreter().HandleCommand(cmd, ret) + foo('target create a.out') + foo('b main') + foo('run') + super(LLDBUI, self).handleEvent(event) + +def main(screen): + signal.signal(signal.SIGINT, sigint_handler) + + global event_queue + event_queue = Queue.Queue() + + global debugger + debugger = lldb.SBDebugger.Create() + + driver = debuggerdriver.createDriver(debugger, event_queue) + view = LLDBUI(screen, event_queue, driver) + + driver.start() + + # hack to avoid hanging waiting for prompts! + driver.handleCommand("settings set auto-confirm true") + + handle_args(driver, sys.argv) + view.eventLoop() + +if __name__ == "__main__": + try: + curses.wrapper(main) + except KeyboardInterrupt: + exit() diff --git a/utils/lui/sandbox.py b/utils/lui/sandbox.py new file mode 100755 index 000000000000..5a3a64cba692 --- /dev/null +++ b/utils/lui/sandbox.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +##===-- sandbox.py -------------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + + + +import curses + +import os +import signal +import sys + +import Queue + +import cui + +event_queue = None + +class SandboxUI(cui.CursesUI): + def __init__(self, screen, event_queue): + super(SandboxUI, self).__init__(screen, event_queue) + + height, width = self.screen.getmaxyx() + w2 = width / 2 + h2 = height / 2 + + self.wins = [] + #self.wins.append(cui.TitledWin(w2, h2, w2, h2, "Test Window 4")) + list_win = cui.ListWin(w2, h2, w2, h2) + for i in range(0, 40): + list_win.addItem('Item %s' % i) + self.wins.append(list_win) + self.wins.append(cui.TitledWin( 0, 0, w2, h2, "Test Window 1")) + self.wins.append(cui.TitledWin(w2, 0, w2, h2, "Test Window 2")) + self.wins.append(cui.TitledWin( 0, h2, w2, h2, "Test Window 3")) + + #def callback(s, content): + # self.wins[0].win.scroll(1) + # self.wins[0].win.addstr(10, 0, '%s: %s' % (s, content)) + # self.wins[0].win.scroll(1) + # self.el.showPrompt(10, 0) + + #self.wins[0].win.scrollok(1) + #self.el = cui.CursesEditLine(self.wins[0].win, None, + # lambda c: callback('got', c), lambda c: callback('tab', c)) + #self.el.prompt = '>>> ' + #self.el.showPrompt(10, 0) + + def handleEvent(self, event): + if isinstance(event, int): + if event == ord('q'): + sys.exit(0) + #self.el.handleEvent(event) + super(SandboxUI, self).handleEvent(event) + +def main(screen): + global event_queue + event_queue = Queue.Queue() + + sandbox = SandboxUI(screen, event_queue) + sandbox.eventLoop() + +if __name__ == "__main__": + try: + curses.wrapper(main) + except KeyboardInterrupt: + exit() diff --git a/utils/lui/sourcewin.py b/utils/lui/sourcewin.py new file mode 100644 index 000000000000..5e7067fdebe2 --- /dev/null +++ b/utils/lui/sourcewin.py @@ -0,0 +1,232 @@ +##===-- sourcewin.py -----------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import cui +import curses +import lldb, lldbutil +import re +import os + +class SourceWin(cui.TitledWin): + def __init__(self, driver, x, y, w, h): + super(SourceWin, self).__init__(x, y, w, h, "Source") + self.sourceman = driver.getSourceManager() + self.sources = {} + + self.filename= None + self.pc_line = None + self.viewline = 0 + + self.breakpoints = { } + + self.win.scrollok(1) + + self.markerPC = ":) " + self.markerBP = "B> " + self.markerNone = " " + + try: + from pygments.formatters import TerminalFormatter + self.formatter = TerminalFormatter() + except ImportError: + #self.win.addstr("\nWarning: no 'pygments' library found. Syntax highlighting is disabled.") + self.lexer = None + self.formatter = None + pass + + # FIXME: syntax highlight broken + self.formatter = None + self.lexer = None + + def handleEvent(self, event): + if isinstance(event, int): + self.handleKey(event) + return + + if isinstance(event, lldb.SBEvent): + if lldb.SBBreakpoint.EventIsBreakpointEvent(event): + self.handleBPEvent(event) + + if lldb.SBProcess.EventIsProcessEvent(event) and \ + not lldb.SBProcess.GetRestartedFromEvent(event): + process = lldb.SBProcess.GetProcessFromEvent(event) + if not process.IsValid(): + return + if process.GetState() == lldb.eStateStopped: + self.refreshSource(process) + elif process.GetState() == lldb.eStateExited: + self.notifyExited(process) + + def notifyExited(self, process): + self.win.erase() + target = lldbutil.get_description(process.GetTarget()) + pid = process.GetProcessID() + ec = process.GetExitStatus() + self.win.addstr("\nProcess %s [%d] has exited with exit-code %d" % (target, pid, ec)) + + def pageUp(self): + if self.viewline > 0: + self.viewline = self.viewline - 1 + self.refreshSource() + + def pageDown(self): + if self.viewline < len(self.content) - self.height + 1: + self.viewline = self.viewline + 1 + self.refreshSource() + pass + + def handleKey(self, key): + if key == curses.KEY_DOWN: + self.pageDown() + elif key == curses.KEY_UP: + self.pageUp() + + def updateViewline(self): + half = self.height / 2 + if self.pc_line < half: + self.viewline = 0 + else: + self.viewline = self.pc_line - half + 1 + + if self.viewline < 0: + raise Exception("negative viewline: pc=%d viewline=%d" % (self.pc_line, self.viewline)) + + def refreshSource(self, process = None): + (self.height, self.width) = self.win.getmaxyx() + + if process is not None: + loc = process.GetSelectedThread().GetSelectedFrame().GetLineEntry() + f = loc.GetFileSpec() + self.pc_line = loc.GetLine() + + if not f.IsValid(): + self.win.addstr(0, 0, "Invalid source file") + return + + self.filename = f.GetFilename() + path = os.path.join(f.GetDirectory(), self.filename) + self.setTitle(path) + self.content = self.getContent(path) + self.updateViewline() + + if self.filename is None: + return + + if self.formatter is not None: + from pygments.lexers import get_lexer_for_filename + self.lexer = get_lexer_for_filename(self.filename) + + bps = [] if not self.filename in self.breakpoints else self.breakpoints[self.filename] + self.win.erase() + if self.content: + self.formatContent(self.content, self.pc_line, bps) + + def getContent(self, path): + content = [] + if path in self.sources: + content = self.sources[path] + else: + if os.path.exists(path): + with open(path) as x: + content = x.readlines() + self.sources[path] = content + return content + + def formatContent(self, content, pc_line, breakpoints): + source = "" + count = 1 + self.win.erase() + end = min(len(content), self.viewline + self.height) + for i in range(self.viewline, end): + line_num = i + 1 + marker = self.markerNone + attr = curses.A_NORMAL + if line_num == pc_line: + attr = curses.A_REVERSE + if line_num in breakpoints: + marker = self.markerBP + line = "%s%3d %s" % (marker, line_num, self.highlight(content[i])) + if len(line) >= self.width: + line = line[0:self.width-1] + "\n" + self.win.addstr(line, attr) + source += line + count = count + 1 + return source + + def highlight(self, source): + if self.lexer and self.formatter: + from pygments import highlight + return highlight(source, self.lexer, self.formatter) + else: + return source + + def addBPLocations(self, locations): + for path in locations: + lines = locations[path] + if path in self.breakpoints: + self.breakpoints[path].update(lines) + else: + self.breakpoints[path] = lines + + def removeBPLocations(self, locations): + for path in locations: + lines = locations[path] + if path in self.breakpoints: + self.breakpoints[path].difference_update(lines) + else: + raise "Removing locations that were never added...no good" + + def handleBPEvent(self, event): + def getLocations(event): + locs = {} + + bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) + + if bp.IsInternal(): + # don't show anything for internal breakpoints + return + + for location in bp: + # hack! getting the LineEntry via SBBreakpointLocation.GetAddress.GetLineEntry does not work good for + # inlined frames, so we get the description (which does take into account inlined functions) and parse it. + desc = lldbutil.get_description(location, lldb.eDescriptionLevelFull) + match = re.search('at\ ([^:]+):([\d]+)', desc) + try: + path = match.group(1) + line = int(match.group(2).strip()) + except ValueError as e: + # bp loc unparsable + continue + + if path in locs: + locs[path].add(line) + else: + locs[path] = set([line]) + return locs + + event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) + if event_type == lldb.eBreakpointEventTypeEnabled \ + or event_type == lldb.eBreakpointEventTypeAdded \ + or event_type == lldb.eBreakpointEventTypeLocationsResolved \ + or event_type == lldb.eBreakpointEventTypeLocationsAdded: + self.addBPLocations(getLocations(event)) + elif event_type == lldb.eBreakpointEventTypeRemoved \ + or event_type == lldb.eBreakpointEventTypeLocationsRemoved \ + or event_type == lldb.eBreakpointEventTypeDisabled: + self.removeBPLocations(getLocations(event)) + elif event_type == lldb.eBreakpointEventTypeCommandChanged \ + or event_type == lldb.eBreakpointEventTypeConditionChanged \ + or event_type == lldb.eBreakpointEventTypeIgnoreChanged \ + or event_type == lldb.eBreakpointEventTypeThreadChanged \ + or event_type == lldb.eBreakpointEventTypeInvalidType: + # no-op + pass + self.refreshSource() + + diff --git a/utils/lui/statuswin.py b/utils/lui/statuswin.py new file mode 100644 index 000000000000..c6f6c990b34d --- /dev/null +++ b/utils/lui/statuswin.py @@ -0,0 +1,40 @@ +##===-- statuswin.py -----------------------------------------*- Python -*-===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import lldb, lldbutil +import cui +import curses + +class StatusWin(cui.TextWin): + def __init__(self, x, y, w, h): + super(StatusWin, self).__init__(x, y, w) + + self.keys = [#('F1', 'Help', curses.KEY_F1), + ('F3', 'Cycle-focus', curses.KEY_F3), + ('F10', 'Quit', curses.KEY_F10)] + + def draw(self): + self.win.addstr(0, 0, '') + for key in self.keys: + self.win.addstr('{0}'.format(key[0]), curses.A_REVERSE) + self.win.addstr(' {0} '.format(key[1]), curses.A_NORMAL) + super(StatusWin, self).draw() + + def handleEvent(self, event): + if isinstance(event, int): + pass + elif isinstance(event, lldb.SBEvent): + if lldb.SBProcess.EventIsProcessEvent(event): + state = lldb.SBProcess.GetStateFromEvent(event) + status = lldbutil.state_type_to_str(state) + self.win.erase() + x = self.win.getmaxyx()[1] - len(status) - 1 + self.win.addstr(0, x, status) + return + diff --git a/utils/misc/grep-svn-log.py b/utils/misc/grep-svn-log.py new file mode 100755 index 000000000000..ebbfe33f0520 --- /dev/null +++ b/utils/misc/grep-svn-log.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +""" +Greps and returns the first svn log entry containing a line matching the regular +expression pattern passed as the only arg. + +Example: + +svn log -v | grep-svn-log.py '^ D.+why_are_you_missing.h$' +""" + +import fileinput, re, sys, StringIO + +# Separator string for "svn log -v" output. +separator = '-' * 72 + +usage = """Usage: grep-svn-log.py line-pattern +Example: + svn log -v | grep-svn-log.py '^ D.+why_are_you_missing.h'""" + +class Log(StringIO.StringIO): + """Simple facade to keep track of the log content.""" + def __init__(self): + self.reset() + def add_line(self, a_line): + """Add a line to the content, if there is a previous line, commit it.""" + global separator + if self.prev_line != None: + print >> self, self.prev_line + self.prev_line = a_line + self.separator_added = (a_line == separator) + def del_line(self): + """Forget about the previous line, do not commit it.""" + self.prev_line = None + def reset(self): + """Forget about the previous lines entered.""" + StringIO.StringIO.__init__(self) + self.prev_line = None + def finish(self): + """Call this when you're finished with populating content.""" + if self.prev_line != None: + print >> self, self.prev_line + self.prev_line = None + +def grep(regexp): + # The log content to be written out once a match is found. + log = Log() + + LOOKING_FOR_MATCH = 0 + FOUND_LINE_MATCH = 1 + state = LOOKING_FOR_MATCH + + while 1: + line = sys.stdin.readline() + if not line: + return + line = line.splitlines()[0] + if state == FOUND_LINE_MATCH: + # At this state, we keep on accumulating lines until the separator + # is encountered. At which point, we can return the log content. + if line == separator: + log.finish() + print log.getvalue() + return + log.add_line(line) + + elif state == LOOKING_FOR_MATCH: + if line == separator: + log.reset() + log.add_line(line) + # Update next state if necessary. + if regexp.search(line): + state = FOUND_LINE_MATCH + +def main(): + if len(sys.argv) != 2: + print usage + sys.exit(0) + + regexp = re.compile(sys.argv[1]) + grep(regexp) + sys.stdin.close() + +if __name__ == '__main__': + main() diff --git a/utils/sync-source/README.txt b/utils/sync-source/README.txt new file mode 100644 index 000000000000..9e019b64d9ad --- /dev/null +++ b/utils/sync-source/README.txt @@ -0,0 +1,293 @@ +syncsource.py + +OVERVIEW + +The syncsource.py utility transfers groups of files between +computers. The primary use case is to enable developing LLVM project +software on one machine, transfer it efficiently to other machines --- +possibly of other architectures --- and test it there. syncsource.py +supports configurable, named source-to-destination mappings and has a +transfer agent plug-in architecture. The current distribution provides +an rsync-over-ssh transfer agent. + +The primary benefits of using syncsource.py are: + +* Provides a simple, reliable way to get a mirror copy of primary- + machine files onto several different destinations without concern + of compromising the patch during testing on different machines. + +* Handles directory-mapping differences between two machines. For + LLDB, this is helpful when going between OS X and any other non-OS X + target system. + +EXAMPLE WORKFLOW + +This utility was developed in the context of working on the LLDB +project. Below we show the transfers we'd like to have happen, +and the configuration that supports it. + +Workflow Example: + +* Develop on OS X (primary machine) +* Test candidate changes on OS X. +* Test candidate changes on a Linux machine (machine-name: lldb-linux). +* Test candidate changes on a FreeBSD machine (machine-name: lldb-freebsd). +* Do check-ins from OS X machine. + +Requirements: + +* OS X machine requires the lldb source layout: lldb, lldb/llvm, + lldb/llvm/tools/clang. Note this is different than the canonical + llvm, llvm/tools/clang, llvm/tools/lldb layout that we'll want on + the Linux and FreeBSD test machines. + +* Linux machine requires the llvm, llvm/tools/clang and + llvm/tools/lldb layout. + +* FreeBSD machine requires the same layout as the llvm machine. + +syncsource.py configuration in ~/.syncsourcerc: + +# This is my configuration with a comment. Configuration +# files are JSON-based. +{ "configurations": [ + # Here we have a collection of named configuration blocks. + # Configuration blocks can chain back to a parent by name. + { + # Every block has a name so it can be referenced from + # the command line or chained back to by a child block + # for sharing. + "name": "base_tot_settings", + + # This directive lists the "directory ids" that we'll care + # about. If your local repository has additional directories + # for other projects that need to ride along, add them here. + # For defaulting purposes, it makes sense to name the + # directory IDs as the most likely name for the directory + # itself. For stock LLDB from top of tree, we generally only + # care about lldb, llvm and clang. + "dir_names": [ "llvm", "clang", "lldb" ], + + # This section describes where the source files live on + # the primary machine. There should always be a base_dir + # entry, which indicates where in the local filesystem the + # projects are rooted. For each dir in dir_names, there + # should be either: + # 1. an entry named {dir-id}_dir (e.g. llvm_dir), which + # specifies the source directory for the given dir id + # relative to the base_dir entry, OR + # 2. no entry, in which case the directory is assumed to + # be the same as {dir-id}. In the example below, the + # base_dir-relative directory for the "lldb" dir-id is + # defaulted to being "lldb". That's exactly what + # we need in an OS X-style lldb dir layout. + "source": { + "base_dir": "~/work/lldb-tot", + "llvm_dir": "lldb/llvm", + "clang_dir": "lldb/llvm/tools/clang" + }, + + # source_excludes covers any exclusions that: + # * should be applied when copying files from the source + # * should be excluded from deletion on the destination + # + # By default, ".git", ".svn" and ".pyc" are added to + # all dir-id exclusions. The default excludes can be + # controlled by the syncsource.py --default-excludes + # option. + # + # Below, I have transfer of the lldb dir skip everything + # rooted at "/llvm" below the the lldb dir. This is + # because we want the source OS X lldb to move to + # a destination of {some-dest-root}/llvm/tools/lldb, and + # not have the OS-X-inverted llvm copy over with the lldb + # transfer portion. We'll see the complete picture of + # how this works when we get to specifying destinations + # later on in the config. + # + # We also exclude the "/build" and "/llvm-build" dir rooted in + # the OS X-side sources. The Xcode configuration on this + # OS X machine will dump lldb builds in the /build directory + # relative to the lldb dir, and it will build llvm+clang in + # the /llvm-build dir relative to the lldb dir. + # + # Note the first forward slash in "/build" indicates to the + # transfer agent that we only want to exclude the + # ~/work/lldb-tot/lldb/build dir, not just any file or + # directory named "build" somewhere underneath the lldb + # directory. Without the leading forward slash, any file + # or directory called build anywhere underneath the lldb dir + # will be excluded, which is definitely not what we want here. + # + # For the llvm dir, we do a source-side exclude for + # "/tools/clang". We manage the clang transfer as a separate + # entity, so we don't want the movement of llvm to also move + # clang. + # + # The llvm_dir exclusion of "/tools/lldb" is the first example + # of an exclude targeting a requirement of the destination + # side. Normally the transfer agent will delete anything on + # the destination that is not present on the source. It is + # trying to mirror, and ensure both sides have the same + # content. The source side of llvm on OS X does not have a + # "/tools/lldb", so at first this exclude looks non-sensical. + # But on the canonical destination layout, lldb lives in + # {some-dest-root}/llvm/tools/lldb. Without this exclude, + # the transfer agent would blow away the tools/lldb directory + # on the destination every time we transfer, and then have to + # copy the lldb dir all over again. For rsync+ssh, that + # totally would defeat the huge transfer efficiencies gained + # by using rsync in the first place. + # + # Note the overloading of both source and dest style excludes + # ultimately comes from the rsync-style exclude mechanism. + # If it wasn't for that, I would have separated source and + # dest excludes out better. + "source_excludes": { + "lldb_dir": ["/llvm", "/build", "/llvm-build"], + "llvm_dir": ["/tools/lldb", "/tools/clang"] + } + }, + + # Top of tree public, common settings for all destinations. + { + # The name for this config block. + "name": "common_tot", + + # Here is our first chaining back to a parent config block. + # Any settings in "common_tot" not specified here are going + # to be retrieved from the parent. + "parent": "base_tot_settings", + + # The transfer agent class to use. Right now, the only one + # available is this one here that uses rsync over ssh. + # If some other mechanism is needed to reach this destination, + # it can be specified here in full [[package.]module.]class form. + "transfer_class": "transfer.rsync.RsyncOverSsh", + + # Specifies the destination-root-relative directories. + # Here our desination is rooted at: + # {some-yet-to-be-specified-destination-root} + "base_dir". + # In other words, each destination will have some kind of root + # for all relative file placement. We'll see those defined + # later, as they can change per destination machine. + # The block below describes the settings relative to that + # destination root. + # + # As before, each dir-id used in this configuration is + # expected to have either: + # 1. an entry named {dir-id}_dir (e.g. llvm_dir), which + # specifies the destination directory for the given dir id + # relative to the dest_root+base_dir entries, OR + # 2. no entry, in which case the directory is assumed to + # be the same as {dir-id}. In the example below, the + # dest_root+base_dir-relative directory for the "llvm" dir-id is + # defaulted to being "llvm". That's exactly what + # we need in a canonical llvm/clang/lldb setup on + # Linux/FreeBSD. + # + # Note we see the untangling of the OS X lldb-centric + # directory structure to the canonical llvm, + # llvm/tools/clang, llvm/tools/lldb structure below. + # We are mapping lldb into a subdirectory of the llvm + # directory. + # + # The transfer logic figures out which directories to copy + # first by finding the shortest destination absolute path + # and doing them in that order. For our case, this ensures + # llvm is copied over before lldb or clang. + "dest": { + "base_dir": "work/mirror/git", + "lldb_dir": "llvm/tools/lldb", + "clang_dir": "llvm/tools/clang" + } + }, + + # Describe the lldb-linux destination. With this, + # we're done with the mapping for transfer setup + # for the lldb-linux box. This configuration can + # be used either by: + # 1. having a parent "default" blockthat points to this one, + # which then gets used by default, or + # 2. using the --configuration/-c CONFIG option to + # specify using this name on the syncsource.py command line. + { + "name": "lldb-linux" + "parent": "common_tot", + + # The ssh block is understood by the rsync+ssh transfer + # agent. Other agents would probably require different + # agent-specific details that they could read from + # other blocks. + "ssh": { + # This specifies the host name (or IP address) as would + # be used as the target for an ssh command. + "dest_host": "lldb-linux.example.com", + + # root_dir specifies the global root directory for + # this destination. All destinations on this target + # will be in a directory that is built from + # root_dir + base_dir + {dir_id}_dir. + "root_dir" : "/home/tfiala", + + # The ssh user is specified here. + "user": "tfiala", + + # The ssh port is specified here. + "port": 22 + } + }, + + # Describe the lldb-freebsd destination. + # Very similar to the lldb-linux one. + { + "name": "lldb-freebsd" + "parent": "common_tot", + "ssh": { + "dest_host": "lldb-freebsd.example.com", + # Specify a different destination-specific root dir here. + "root_dir" : "/mnt/ssd02/fialato", + "user": "fialato", + # The ssh port is specified here. + "port": 2022 + } + }, + + # If a block named "default" exists, and if no configuration + # is specified on the command line, then the default block + # will be used. Use this block to point to the most common + # transfer destination you would use. + { + "name": "default", + "parent": "lldb-linux" + } +] +} + +Using it + +Now that we have a .syncsourcerc file set up, we can do a transfer. +The .syncsourcerc file will be searched for as follows, using the +first one that is found: + +* First check the --rc-file RCFILE option. If this is specified + and doesn't exist, it will raise an error and quit. + +* Check if the current directory has a .syncsourcerc file. If so, + use that. + +* Use the .syncsourcerc file from the user's home directory. + +Run the command: +python /path/to/syncsource.rc -c {configuration-name} + +The -c {configuration-name} can be left off, in which case a +configuration with the name 'default' will be used. + +After that, the transfer will occur. With the rsync-over-ssh +transfer agent, one rsync per dir-id will be used. rsync output +is redirected to the console. + +FEEDBACK + +Feel free to pass feedback along to Todd Fiala (todd.fiala@gmail.com). diff --git a/utils/sync-source/lib/transfer/__init__.py b/utils/sync-source/lib/transfer/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/utils/sync-source/lib/transfer/__init__.py diff --git a/utils/sync-source/lib/transfer/protocol.py b/utils/sync-source/lib/transfer/protocol.py new file mode 100644 index 000000000000..6a4e6e0a3a45 --- /dev/null +++ b/utils/sync-source/lib/transfer/protocol.py @@ -0,0 +1,7 @@ +class Protocol(object): + def __init__(self, options, config): + self.options = options + self.config = config + + def transfer(transfer_specs, dry_run): + raise "transfer must be overridden by transfer implementation" diff --git a/utils/sync-source/lib/transfer/rsync.py b/utils/sync-source/lib/transfer/rsync.py new file mode 100644 index 000000000000..7a89344b6991 --- /dev/null +++ b/utils/sync-source/lib/transfer/rsync.py @@ -0,0 +1,60 @@ +import os.path +import pprint +import subprocess +import sys + +import transfer.protocol + + +class RsyncOverSsh(transfer.protocol.Protocol): + def __init__(self, options, config): + super(RsyncOverSsh, self).__init__(options, config) + self.ssh_config = config.get_value("ssh") + + def build_rsync_command(self, transfer_spec, dry_run): + dest_path = os.path.join( + self.ssh_config["root_dir"], + transfer_spec.dest_path) + flags = "-avz" + if dry_run: + flags += "n" + cmd = [ + "rsync", + flags, + "-e", + "ssh -p {}".format(self.ssh_config["port"]), + "--rsync-path", + # The following command needs to know the right way to do + # this on the dest platform - ensures the target dir exists. + "mkdir -p {} && rsync".format(dest_path) + ] + + # Add source dir exclusions + if transfer_spec.exclude_paths: + for exclude_path in transfer_spec.exclude_paths: + cmd.append("--exclude") + cmd.append(exclude_path) + + cmd.extend([ + "--delete", + transfer_spec.source_path + "/", + "{}@{}:{}".format( + self.ssh_config["user"], + self.ssh_config["dest_host"], + dest_path)]) + return cmd + + def transfer(self, transfer_specs, dry_run): + if self.options.verbose: + printer = pprint.PrettyPrinter() + for spec in transfer_specs: + printer.pprint(spec) + + for spec in transfer_specs: + cmd = self.build_rsync_command(spec, dry_run) + if self.options.verbose: + print "executing the following command:\n{}".format(cmd) + result = subprocess.call( + cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) + if result != 0: + return result diff --git a/utils/sync-source/lib/transfer/transfer_spec.py b/utils/sync-source/lib/transfer/transfer_spec.py new file mode 100644 index 000000000000..cc76c70bf41c --- /dev/null +++ b/utils/sync-source/lib/transfer/transfer_spec.py @@ -0,0 +1,11 @@ +class TransferSpec(object): + def __init__(self, source_path, exclude_paths, dest_path): + self.source_path = source_path + self.exclude_paths = exclude_paths + self.dest_path = dest_path + + def __repr__(self): + fmt = ( + "TransferSpec(source_path='{}', exclude_paths='{}', " + "dest_path='{}')") + return fmt.format(self.source_path, self.exclude_paths, self.dest_path) diff --git a/utils/sync-source/pylintrc b/utils/sync-source/pylintrc new file mode 100644 index 000000000000..d20e5bd4dea4 --- /dev/null +++ b/utils/sync-source/pylintrc @@ -0,0 +1,2 @@ +[Master] +init-hook='import os; import sys; sys.path.append(os.path.join(os.getcwd(), "lib")); print("hello from {}".format(os.getcwd()))' diff --git a/utils/sync-source/syncsource.py b/utils/sync-source/syncsource.py new file mode 100644 index 000000000000..736aefd9a35c --- /dev/null +++ b/utils/sync-source/syncsource.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +""" + The LLVM Compiler Infrastructure + +This file is distributed under the University of Illinois Open Source +License. See LICENSE.TXT for details. + +Sync lldb and related source from a local machine to a remote machine. + +This facilitates working on the lldb sourcecode on multiple machines +and multiple OS types, verifying changes across all. +""" + +import argparse +import cStringIO +import importlib +import json +import os.path +import re +import sys + +# Add the local lib directory to the python path. +LOCAL_LIB_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "lib") +sys.path.append(LOCAL_LIB_PATH) + +import transfer.transfer_spec + + +DOTRC_BASE_FILENAME = ".syncsourcerc" + + +class Configuration(object): + """Provides chaining configuration lookup.""" + def __init__(self, rcdata_configs): + self.__rcdata_configs = rcdata_configs + + def get_value(self, key): + """ + Return the first value in the parent chain that has the key. + + The traversal starts from the most derived configuration (i.e. + child) and works all the way up the parent chain. + + @return the value of the first key in the parent chain that + contains a value for the given key. + """ + for config in self.__rcdata_configs: + if key in config: + return config[key] + return None + + def __getitem__(self, key): + value = self.get_value(key) + if value: + return value + else: + raise KeyError(key) + + +def parse_args(): + """@return options parsed from the command line.""" + parser = argparse.ArgumentParser() + parser.add_argument( + "--config-name", "-c", action="store", default="default", + help="specify configuration name to use") + parser.add_argument( + "--default-excludes", action="store", default="*.git,*.svn,*.pyc", + help=("comma-separated list of default file patterns to exclude " + "from each source directory and to protect from deletion " + "on each destination directory; if starting with forward " + "slash, it only matches at the top of the base directory")) + parser.add_argument( + "--dry-run", "-n", action="store_true", + help="do a dry run of the transfer operation, don't really transfer") + parser.add_argument( + "--rc-file", "-r", action="store", + help="specify the sync-source rc file to use for configurations") + parser.add_argument( + "--verbose", "-v", action="store_true", help="turn on verbose output") + return parser.parse_args() + + +def read_rcfile(filename): + """Returns the json-parsed contents of the input file.""" + + # First parse file contents, removing all comments but + # preserving the line count. + regex = re.compile(r"#.*$") + + comment_stripped_file = cStringIO.StringIO() + with open(filename, "r") as json_file: + for line in json_file: + comment_stripped_file.write(regex.sub("", line)) + return json.load(cStringIO.StringIO(comment_stripped_file.getvalue())) + + +def find_appropriate_rcfile(options): + # Use an options-specified rcfile if specified. + if options.rc_file and len(options.rc_file) > 0: + if not os.path.isfile(options.rc_file): + # If it doesn't exist, error out here. + raise "rcfile '{}' specified but doesn't exist".format( + options.rc_file) + return options.rc_file + + # Check if current directory .sync-sourcerc exists. If so, use it. + local_rc_filename = os.path.abspath(DOTRC_BASE_FILENAME) + if os.path.isfile(local_rc_filename): + return local_rc_filename + + # Check if home directory .sync-sourcerc exists. If so, use it. + homedir_rc_filename = os.path.abspath( + os.path.join(os.path.expanduser("~"), DOTRC_BASE_FILENAME)) + if os.path.isfile(homedir_rc_filename): + return homedir_rc_filename + + # Nothing matched. We don't have an rc filename candidate. + return None + + +def get_configuration(options, rcdata, config_name): + rcdata_configs = [] + next_config_name = config_name + while next_config_name: + # Find the next rcdata configuration for the given name. + rcdata_config = next( + config for config in rcdata["configurations"] + if config["name"] == next_config_name) + + # See if we found it. + if rcdata_config: + # This is our next configuration to use in the chain. + rcdata_configs.append(rcdata_config) + + # If we have a parent, check that next. + if "parent" in rcdata_config: + next_config_name = rcdata_config["parent"] + else: + next_config_name = None + else: + raise "failed to find specified parent config '{}'".format( + next_config_name) + return Configuration(rcdata_configs) + + +def create_transfer_agent(options, configuration): + transfer_class_spec = configuration.get_value("transfer_class") + if options.verbose: + print "specified transfer class: '{}'".format(transfer_class_spec) + + # Load the module (possibly package-qualified). + components = transfer_class_spec.split(".") + module = importlib.import_module(".".join(components[:-1])) + + # Create the class name we need to load. + clazz = getattr(module, components[-1]) + return clazz(options, configuration) + + +def sync_configured_sources(options, configuration, default_excludes): + # Look up the transfer method. + transfer_agent = create_transfer_agent(options, configuration) + + # For each configured dir_names source, do the following transfer: + # 1. Start with base_dir + {source-dir-name}_dir + # 2. Copy all files recursively, but exclude + # all dirs specified by source_excludes: + # skip all base_dir + {source-dir-name}_dir + + # {source-dir-name}_dir excludes. + source_dirs = configuration.get_value("source") + source_excludes = configuration.get_value("source_excludes") + dest_dirs = configuration.get_value("dest") + + source_base_dir = source_dirs["base_dir"] + dest_base_dir = dest_dirs["base_dir"] + dir_ids = configuration.get_value("dir_names") + transfer_specs = [] + + for dir_id in dir_ids: + dir_key = "{}_dir".format(dir_id) + + # Build the source dir (absolute) that we're copying from. + # Defaults the base-relative source dir to the source id (e.g. lldb) + rel_source_dir = source_dirs.get(dir_key, dir_id) + transfer_source_dir = os.path.expanduser( + os.path.join(source_base_dir, rel_source_dir)) + + # Exclude dirs do two things: + # 1) stop items from being copied on the source side, and + # 2) protect things from being deleted on the dest side. + # + # In both cases, they are specified relative to the base + # directory on either the source or dest side. + # + # Specifying a leading '/' in the directory will limit it to + # be rooted in the base directory. i.e. "/.git" will only + # match {base-dir}/.git, not {base-dir}/subdir/.git, but + # ".svn" will match {base-dir}/.svn and + # {base-dir}/subdir/.svn. + # + # If excludes are specified for this dir_id, then pass along + # the excludes. These are relative to the dir_id directory + # source, and get passed along that way as well. + transfer_source_excludes = [] + + # Add the source excludes for this dir. + skip_defaults = False + if source_excludes and dir_key in source_excludes: + transfer_source_excludes.extend(source_excludes[dir_key]) + if "<no-defaults>" in source_excludes[dir_key]: + skip_defaults = True + transfer_source_excludes.remove("<no-defaults>") + + if not skip_defaults and default_excludes is not None: + transfer_source_excludes.extend(list(default_excludes)) + + # Build the destination-base-relative dest dir into which + # we'll be syncing. Relative directory defaults to the + # dir id + rel_dest_dir = dest_dirs.get(dir_key, dir_id) + transfer_dest_dir = os.path.join(dest_base_dir, rel_dest_dir) + + # Add the exploded paths to the list that we'll ask the + # transfer agent to transfer for us. + transfer_specs.append( + transfer.transfer_spec.TransferSpec( + transfer_source_dir, + transfer_source_excludes, + transfer_dest_dir)) + + # Do the transfer. + if len(transfer_specs) > 0: + transfer_agent.transfer(transfer_specs, options.dry_run) + else: + raise Exception("nothing to transfer, bad configuration?") + + +def main(): + """Drives the main program.""" + options = parse_args() + + if options.default_excludes and len(options.default_excludes) > 0: + default_excludes = options.default_excludes.split(",") + else: + default_excludes = [] + + # Locate the rc filename to load, then load it. + rc_filename = find_appropriate_rcfile(options) + if rc_filename: + if options.verbose: + print "reading rc data from file '{}'".format(rc_filename) + rcdata = read_rcfile(rc_filename) + else: + sys.stderr.write("no rcfile specified, cannot guess configuration") + exit(1) + + # Find configuration. + configuration = get_configuration(options, rcdata, options.config_name) + if not configuration: + sys.stderr.write("failed to find configuration for {}".format( + options.config_data)) + exit(2) + + # Kick off the transfer. + sync_configured_sources(options, configuration, default_excludes) + +if __name__ == "__main__": + main() diff --git a/utils/test/README-disasm b/utils/test/README-disasm new file mode 100644 index 000000000000..00e9ab681a24 --- /dev/null +++ b/utils/test/README-disasm @@ -0,0 +1,406 @@ +This README describes a sample invocation of disasm.py whose purpose is to test +the low level ARM/Thumb disassembly functionality from llvm using the llvm-mc +command line. We invoke gdb on an executable, try to disassemble a function, +and then read the memory contents of the disassembled function. + +The byte contents are written into a file named disasm-input.txt and then we +invoke llvm-mc -disassemble plus options (set with the -o/--options) on the +byte contents. + +See the following for a sample session using this command: + +[16:26:57] johnny:/Volumes/data/Radar/9131529 $ /Volumes/data/lldb/svn/trunk/utils/test/disasm.py -C 'set shlib-path-substitutions /usr /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr /System /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System /Library /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/Library' -O '-arch armv7' -m /Volumes/data/lldb/llvm/Debug+Asserts/bin/llvm-mc -e /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib -f printf --options='-triple=thumb-apple-darwin -debug-only=arm-disassembler' +gdb commands: ['set shlib-path-substitutions /usr /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr /System /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System /Library /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/Library'] +gdb options: -arch armv7 +executable: /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib +function: printf +llvm-mc: /Volumes/data/lldb/llvm/Debug+Asserts/bin/llvm-mc +llvm-mc options: -triple=thumb-apple-darwin -debug-only=arm-disassembler +GNU gdb 6.3.50-20050815 (Apple version gdb-1518) (Sat Feb 12 02:56:02 UTC 2011) +Copyright 2004 Free Software Foundation, Inc. +GDB is free software, covered by the GNU General Public License, and you are +welcome to change it and/or distribute copies of it under certain conditions. +Type "show copying" to see the conditions. +There is absolutely no warranty for GDB. Type "show warranty" for details. +This GDB was configured as "--host=x86_64-apple-darwin --target=arm-apple-darwin". +<Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/Library +<eloper/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib +Reading symbols for shared libraries ................ done +Reading symbols from /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib...done. +(gdb) disassemble printf +Dump of assembler code for function printf: +0x0704cdd0 <printf+0>: push {r0, r1, r2, r3} +0x0704cdd2 <printf+2>: push {r4, r5, r7, lr} +0x0704cdd4 <printf+4>: add r7, sp, #8 +0x0704cdd6 <printf+6>: sub sp, #4 +0x0704cdd8 <printf+8>: add r3, sp, #20 +0x0704cdda <printf+10>: ldr.w r5, [r3], #4 +0x0704cdde <printf+14>: str r3, [sp, #0] +0x0704cde0 <printf+16>: ldr r3, [pc, #52] (0x704ce18 <printf+72>) +0x0704cde2 <printf+18>: add r3, pc +0x0704cde4 <printf+20>: ldr r0, [r3, #0] +0x0704cde6 <printf+22>: ldr r4, [r0, #0] +0x0704cde8 <printf+24>: ldr r0, [pc, #48] (0x704ce1c <printf+76>) +0x0704cdea <printf+26>: add r0, pc +0x0704cdec <printf+28>: ldr r0, [r0, #0] +0x0704cdee <printf+30>: ldr r0, [r0, #0] +0x0704cdf0 <printf+32>: blx 0x707ba30 <pthread_getspecific> +0x0704cdf4 <printf+36>: cbnz r0, 0x704cdfe <printf+46> +0x0704cdf6 <printf+38>: ldr r1, [pc, #40] (0x704ce20 <printf+80>) +0x0704cdf8 <printf+40>: add r1, pc +0x0704cdfa <printf+42>: ldr r1, [r1, #0] +0x0704cdfc <printf+44>: b.n 0x704ce00 <printf+48> +0x0704cdfe <printf+46>: mov r1, r0 +0x0704ce00 <printf+48>: mov r0, r4 +0x0704ce02 <printf+50>: mov r2, r5 +0x0704ce04 <printf+52>: ldr r3, [sp, #0] +0x0704ce06 <printf+54>: bl 0x704ad44 <vfprintf_l> +0x0704ce0a <printf+58>: sub.w sp, r7, #8 ; 0x8 +0x0704ce0e <printf+62>: ldmia.w sp!, {r4, r5, r7, lr} +0x0704ce12 <printf+66>: add sp, #16 +0x0704ce14 <printf+68>: bx lr +0x0704ce16 <printf+70>: nop +0x0704ce18 <printf+72>: movs r3, #142 +0x0704ce1a <printf+74>: lsls r5, r0, #0 +0x0704ce1c <printf+76>: adds r1, #122 +0x0704ce1e <printf+78>: lsls r5, r0, #0 +0x0704ce20 <printf+80>: adds r1, #104 +0x0704ce22 <printf+82>: lsls r5, r0, #0 +End of assembler dump. +(gdb) x /2b 0x0704cdd0 +0x704cdd0 <printf>: 0x0f 0xb4 +(gdb) x /2b 0x0704cdd2 +0x704cdd2 <printf+2>: 0xb0 0xb5 +(gdb) x /2b 0x0704cdd4 +0x704cdd4 <printf+4>: 0x02 0xaf +(gdb) x /2b 0x0704cdd6 +0x704cdd6 <printf+6>: 0x81 0xb0 +(gdb) x /2b 0x0704cdd8 +0x704cdd8 <printf+8>: 0x05 0xab +(gdb) x /4b 0x0704cdda +0x704cdda <printf+10>: 0x53 0xf8 0x04 0x5b +(gdb) x /2b 0x0704cdde +0x704cdde <printf+14>: 0x00 0x93 +(gdb) x /2b 0x0704cde0 +0x704cde0 <printf+16>: 0x0d 0x4b +(gdb) x /2b 0x0704cde2 +0x704cde2 <printf+18>: 0x7b 0x44 +(gdb) x /2b 0x0704cde4 +0x704cde4 <printf+20>: 0x18 0x68 +(gdb) x /2b 0x0704cde6 +0x704cde6 <printf+22>: 0x04 0x68 +(gdb) x /2b 0x0704cde8 +0x704cde8 <printf+24>: 0x0c 0x48 +(gdb) x /2b 0x0704cdea +0x704cdea <printf+26>: 0x78 0x44 +(gdb) x /2b 0x0704cdec +0x704cdec <printf+28>: 0x00 0x68 +(gdb) x /2b 0x0704cdee +0x704cdee <printf+30>: 0x00 0x68 +(gdb) x /4b 0x0704cdf0 +0x704cdf0 <printf+32>: 0x2e 0xf0 0x1e 0xee +(gdb) x /2b 0x0704cdf4 +0x704cdf4 <printf+36>: 0x18 0xb9 +(gdb) x /2b 0x0704cdf6 +0x704cdf6 <printf+38>: 0x0a 0x49 +(gdb) x /2b 0x0704cdf8 +0x704cdf8 <printf+40>: 0x79 0x44 +(gdb) x /2b 0x0704cdfa +0x704cdfa <printf+42>: 0x09 0x68 +(gdb) x /2b 0x0704cdfc +0x704cdfc <printf+44>: 0x00 0xe0 +(gdb) x /2b 0x0704cdfe +0x704cdfe <printf+46>: 0x01 0x46 +(gdb) x /2b 0x0704ce00 +0x704ce00 <printf+48>: 0x20 0x46 +(gdb) x /2b 0x0704ce02 +0x704ce02 <printf+50>: 0x2a 0x46 +(gdb) x /2b 0x0704ce04 +0x704ce04 <printf+52>: 0x00 0x9b +(gdb) x /4b 0x0704ce06 +0x704ce06 <printf+54>: 0xfd 0xf7 0x9d 0xff +(gdb) x /4b 0x0704ce0a +0x704ce0a <printf+58>: 0xa7 0xf1 0x08 0x0d +(gdb) x /4b 0x0704ce0e +0x704ce0e <printf+62>: 0xbd 0xe8 0xb0 0x40 +(gdb) x /2b 0x0704ce12 +0x704ce12 <printf+66>: 0x04 0xb0 +(gdb) x /2b 0x0704ce14 +0x704ce14 <printf+68>: 0x70 0x47 +(gdb) x /2b 0x0704ce16 +0x704ce16 <printf+70>: 0x00 0xbf +(gdb) x /2b 0x0704ce18 +0x704ce18 <printf+72>: 0x8e 0x23 +(gdb) x /2b 0x0704ce1a +0x704ce1a <printf+74>: 0x05 0x00 +(gdb) x /2b 0x0704ce1c +0x704ce1c <printf+76>: 0x7a 0x31 +(gdb) x /2b 0x0704ce1e +0x704ce1e <printf+78>: 0x05 0x00 +(gdb) x /2b 0x0704ce20 +0x704ce20 <printf+80>: 0x68 0x31 +(gdb) x /2b 0x0704ce22 +0x704ce22 <printf+82>: 0x05 0x00 +(gdb) quit + +Executing command: /Volumes/data/lldb/llvm/Debug+Asserts/bin/llvm-mc -disassemble -triple=thumb-apple-darwin -debug-only=arm-disassembler disasm-input.txt +Opcode=2305 Name=tPUSH Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 0: 1: 0: 0| 0: 0: 0: 0| 1: 1: 1: 1| +------------------------------------------------------------------------------------------------- + + push {r0, r1, r2, r3} +Opcode=2305 Name=tPUSH Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 0: 1: 0: 1| 1: 0: 1: 1| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + push {r4, r5, r7, lr} +Opcode=2228 Name=tADDrSPi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 0| 1: 1: 1: 1| 0: 0: 0: 0| 0: 0: 1: 0| +------------------------------------------------------------------------------------------------- + + add r7, sp, #8 +Opcode=2328 Name=tSUBspi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 0: 0: 0: 0| 1: 0: 0: 0| 0: 0: 0: 1| +------------------------------------------------------------------------------------------------- + + sub sp, #4 +Opcode=2228 Name=tADDrSPi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 0| 1: 0: 1: 1| 0: 0: 0: 0| 0: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + add r3, sp, #20 +Opcode=1963 Name=t2LDR_POST Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 1: 1: 1: 1| 1: 0: 0: 0| 0: 1: 0: 1| 0: 0: 1: 1| 0: 1: 0: 1| 1: 0: 1: 1| 0: 0: 0: 0| 0: 1: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r5, [r3], #4 +Opcode=2324 Name=tSTRspi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 0: 1| 0: 0: 1: 1| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + str r3, [sp] +Opcode=2275 Name=tLDRpci Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 1: 0: 1: 1| 0: 0: 0: 0| 1: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + ldr.n r3, #52 +Opcode=2223 Name=tADDhirr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 0: 0| 0: 1: 1: 1| 1: 0: 1: 1| +------------------------------------------------------------------------------------------------- + + add r3, pc +Opcode=2274 Name=tLDRi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 1: 0| 1: 0: 0: 0| 0: 0: 0: 1| 1: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r0, [r3] +Opcode=2274 Name=tLDRi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 1: 0| 1: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r4, [r0] +Opcode=2275 Name=tLDRpci Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 1: 0: 0: 0| 0: 0: 0: 0| 1: 1: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr.n r0, #48 +Opcode=2223 Name=tADDhirr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 0: 0| 0: 1: 1: 1| 1: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + add r0, pc +Opcode=2274 Name=tLDRi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 1: 0| 1: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r0, [r0] +Opcode=2274 Name=tLDRi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 1: 0| 1: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r0, [r0] +Opcode=2243 Name=tBLXi_r9 Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 1: 1: 1: 1| 0: 0: 0: 0| 0: 0: 1: 0| 1: 1: 1: 0| 1: 1: 1: 0| 1: 1: 1: 0| 0: 0: 0: 1| 1: 1: 1: 0| +------------------------------------------------------------------------------------------------- + + blx #191548 +Opcode=2255 Name=tCBNZ Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 1: 0: 0: 1| 0: 0: 0: 1| 1: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + cbnz r0, #6 +Opcode=2275 Name=tLDRpci Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 1: 0: 0: 1| 0: 0: 0: 0| 1: 0: 1: 0| +------------------------------------------------------------------------------------------------- + + ldr.n r1, #40 +Opcode=2223 Name=tADDhirr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 0: 0| 0: 1: 1: 1| 1: 0: 0: 1| +------------------------------------------------------------------------------------------------- + + add r1, pc +Opcode=2274 Name=tLDRi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 1: 0| 1: 0: 0: 0| 0: 0: 0: 0| 1: 0: 0: 1| +------------------------------------------------------------------------------------------------- + + ldr r1, [r1] +Opcode=2238 Name=tB Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 1: 1: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + b #0 +Opcode=2294 Name=tMOVr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 1: 0| 0: 0: 0: 0| 0: 0: 0: 1| +------------------------------------------------------------------------------------------------- + + mov r1, r0 +Opcode=2294 Name=tMOVr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 1: 0| 0: 0: 1: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + mov r0, r4 +Opcode=2294 Name=tMOVr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 1: 0| 0: 0: 1: 0| 1: 0: 1: 0| +------------------------------------------------------------------------------------------------- + + mov r2, r5 +Opcode=2278 Name=tLDRspi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 0: 1| 1: 0: 1: 1| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + ldr r3, [sp] +Opcode=2246 Name=tBLr9 Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 1: 1: 1: 1| 0: 1: 1: 1| 1: 1: 1: 1| 1: 1: 0: 1| 1: 1: 1: 1| 1: 1: 1: 1| 1: 0: 0: 1| 1: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + bl #-8390 +Opcode=2153 Name=t2SUBri Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 1: 1: 1: 1| 0: 0: 0: 1| 1: 0: 1: 0| 0: 1: 1: 1| 0: 0: 0: 0| 1: 1: 0: 1| 0: 0: 0: 0| 1: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + sub.w sp, r7, #8 +Opcode=1926 Name=t2LDMIA_UPD Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 1: 1: 1: 0| 1: 0: 0: 0| 1: 0: 1: 1| 1: 1: 0: 1| 0: 1: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + pop.w {r4, r5, r7, lr} +Opcode=2230 Name=tADDspi Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| +------------------------------------------------------------------------------------------------- + + add sp, #16 +Opcode=2250 Name=tBX_RET Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 0| 0: 1: 1: 1| 0: 1: 1: 1| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + bx lr +Opcode=2300 Name=tNOP Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 1: 0: 1: 1| 1: 1: 1: 1| 0: 0: 0: 0| 0: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + nop +Opcode=2293 Name=tMOVi8 Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 1: 0| 0: 0: 1: 1| 1: 0: 0: 0| 1: 1: 1: 0| +------------------------------------------------------------------------------------------------- + + movs r3, #142 +Opcode=2290 Name=tMOVSr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + movs r5, r0 +Opcode=2225 Name=tADDi8 Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 1: 1| 0: 0: 0: 1| 0: 1: 1: 1| 1: 0: 1: 0| +------------------------------------------------------------------------------------------------- + + adds r1, #122 +Opcode=2290 Name=tMOVSr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + movs r5, r0 +Opcode=2225 Name=tADDi8 Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 1: 1| 0: 0: 0: 1| 0: 1: 1: 0| 1: 0: 0: 0| +------------------------------------------------------------------------------------------------- + + adds r1, #104 +Opcode=2290 Name=tMOVSr Format=ARM_FORMAT_THUMBFRM(25) + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +------------------------------------------------------------------------------------------------- +| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 0: 0: 0| 0: 1: 0: 1| +------------------------------------------------------------------------------------------------- + + movs r5, r0 +[16:28:00] johnny:/Volumes/data/Radar/9131529 $ diff --git a/utils/test/README-lldb-disasm b/utils/test/README-lldb-disasm new file mode 100644 index 000000000000..328658c326b1 --- /dev/null +++ b/utils/test/README-lldb-disasm @@ -0,0 +1,94 @@ +This README describes a sample invocation of lldb-disasm.py whose purpose is to test +the lldb 'disassemble' command. + +This is for the initial checkin of lldb-disasm.py which only reads an executable image and +dumps the symbol table from the imgae and its dependent libraries. The output was cut off +since it is too large. + +da0603a-dhcp191:9131529 johnny$ /Volumes/data/lldb/svn/trunk/utils/test/lldb-disasm.py -C 'platform create remote-ios' -e /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib +lldb commands: ['platform create remote-ios'] +lldb options: None +executable: /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib +sys.path: ['/Volumes/data/lldb/svn/trunk/utils/test', '/Volumes/data/lldb/svn/trunk/build/Debug/LLDB.framework/Resources/Python', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python26.zip', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-darwin', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-mac', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-mac/lib-scriptpackages', '/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-old', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-dynload', '/Library/Python/2.6/site-packages', '/AppleInternal/Library/Python/2.6/site-packages', '/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC', '/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/wx-2.8-mac-unicode', '/Volumes/data/lldb/svn/trunk/utils/test/../../test/pexpect-2.4', '/Volumes/data/lldb/svn/trunk/test'] +/Volumes/data/lldb/svn/trunk/test/lldbutil.py:80: SyntaxWarning: import * only allowed at module level + def int_to_bytearray(val, bytesize): +/Volumes/data/lldb/svn/trunk/test/lldbutil.py:105: SyntaxWarning: import * only allowed at module level + def bytearray_to_int(bytes, bytesize): +run command: platform create remote-ios +output: Platform: remote-ios +Not connected to a remote platform. +SDKROOT: "/Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3 (8F190)" + +run command: file /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib +output: Current executable set to '/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib' (armv7). + +run command: image dump symtab +output: Dumping symbol table for 18 modules. +Symtab, file = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/usr/lib/libSystem.B.dylib, num_symbols = 851: + Debug symbol + |Synthetic symbol + ||Externally Visible + ||| +Index UserID DSX Type File Address/Value Load Address Size Flags Name +------- ------ --- ------------ ------------------ ------------------ ------------------ ---------- ---------------------------------- +[ 0] 0 Code 0x0000000000001420 0x0000000000000000 0x000e0008 libSystem_initializer +[ 1] 1 Code 0x00000000000014c4 0x0000000000000000 0x001e0008 __keymgr_initializer +[ 2] 2 Code 0x00000000000014fc 0x0000000000000000 0x000e0008 dwarf2_unwind_dyld_add_image_hook +[ 3] 3 Code 0x0000000000001564 0x0000000000000000 0x000e0008 get_or_create_key_element +[ 4] 4 Code 0x0000000000001684 0x0000000000000000 0x000e0008 unlock_node +[ 5] 5 Code 0x0000000000001930 0x0000000000000000 0x000e0000 RsqrtTable +[ 6] 6 Code 0x0000000000001c30 0x0000000000000000 0x000e0000 acosf_crossover +[ 7] 7 Code 0x0000000000001c34 0x0000000000000000 0x000e0000 acosf_mid_poly +[ 8] 8 Code 0x0000000000001c48 0x0000000000000000 0x000e0000 Pi2_Q30 +[ 9] 9 Code 0x0000000000001c4c 0x0000000000000000 0x000e0000 Pi_Q30 +[ 10] 10 Code 0x0000000000001c78 0x0000000000000000 0x000e0000 acosf_approx +[ 11] 11 Code 0x0000000000001cec 0x0000000000000000 0x000e0000 acosf_pos_tail_poly +[ 12] 12 Code 0x0000000000001d00 0x0000000000000000 0x000e0000 acosf_tail +[ 13] 13 Code 0x0000000000001dfc 0x0000000000000000 0x000e0000 acosf_normalize +[ 14] 14 Code 0x0000000000001e10 0x0000000000000000 0x000e0000 acosf_round +[ 15] 15 Code 0x0000000000001e28 0x0000000000000000 0x000e0000 acosf_encode +[ 16] 16 Code 0x0000000000001e30 0x0000000000000000 0x000e0000 acosf_done +[ 17] 17 Code 0x0000000000001e38 0x0000000000000000 0x000e0000 acosf_special +[ 18] 18 Code 0x0000000000001e68 0x0000000000000000 0x000e0000 acosf_small +[ 19] 19 Code 0x0000000000001e9c 0x0000000000000000 0x000e0000 acosf_very_small +[ 20] 20 Code 0x0000000000001eb8 0x0000000000000000 0x000e0000 Pif +[ 21] 21 Code 0x000000000000220c 0x0000000000000000 0x000e0000 RsqrtTable +[ 22] 22 Code 0x000000000000250c 0x0000000000000000 0x000e0000 asinf_crossover +[ 23] 23 Code 0x0000000000002510 0x0000000000000000 0x000e0000 asinf_mid_poly +[ 24] 24 Code 0x0000000000002524 0x0000000000000000 0x000e0000 Pi2_Q30 +[ 25] 25 Code 0x0000000000002550 0x0000000000000000 0x000e0000 asinf_approx +[ 26] 26 Code 0x00000000000025e4 0x0000000000000000 0x000e0000 asinf_tail_poly +[ 27] 27 Code 0x0000000000002600 0x0000000000000000 0x000e0000 asinf_tail +[ 28] 28 Code 0x00000000000026e0 0x0000000000000000 0x000e0000 asinf_normalize +[ 29] 29 Code 0x00000000000026f4 0x0000000000000000 0x000e0000 asinf_round +[ 30] 30 Code 0x000000000000270c 0x0000000000000000 0x000e0000 asinf_encode +[ 31] 31 Code 0x0000000000002718 0x0000000000000000 0x000e0000 asinf_done +[ 32] 32 Code 0x0000000000002720 0x0000000000000000 0x000e0000 asinf_special +[ 33] 33 Code 0x0000000000002754 0x0000000000000000 0x000e0000 asinf_small +[ 34] 34 Code 0x0000000000002784 0x0000000000000000 0x000e0000 Pi2f +[ 35] 35 Code 0x0000000000005774 0x0000000000000000 0x000e0008 rem_pio2 +[ 36] 36 Code 0x00000000000076c4 0x0000000000000000 0x000e0008 __kernel_rem_pio2 +[ 37] 37 Code 0x0000000000008c90 0x0000000000000000 0x000e0008 __kernel_tan +[ 38] 38 Code 0x0000000000008ef0 0x0000000000000000 0x000e0008 lgammaApprox +[ 39] 39 Code 0x000000000000b3d4 0x0000000000000000 0x000e0000 powf_not_special +[ 40] 40 Code 0x000000000000b3dc 0x0000000000000000 0x000e0000 powf_ylgx +[ 41] 41 Code 0x000000000000b438 0x0000000000000000 0x000e0000 powf_done +[ 42] 42 Code 0x000000000000b43c 0x0000000000000000 0x000e0000 powf_special_y +[ 43] 43 Code 0x000000000000b4a8 0x0000000000000000 0x000e0000 powf_special_x +[ 44] 44 Code 0x000000000000b4cc 0x0000000000000000 0x000e0000 powf_mzero_minf +[ 45] 45 Code 0x000000000000b54c 0x0000000000000000 0x000e0000 powf_y_odd +[ 46] 46 Code 0x000000000000b57c 0x0000000000000000 0x000e0000 powf_y_nonint +[ 47] 47 Code 0x000000000000b588 0x0000000000000000 0x000e0000 powf_y_even +[ 48] 48 Code 0x000000000000b7a8 0x0000000000000000 0x000e0000 powf_log2_reduction +[ 49] 49 Code 0x000000000000b7a8 0x0000000000000000 0x000e0000 powf_log2 +[ 50] 50 Code 0x000000000000b814 0x0000000000000000 0x000e0000 powf_log2_approx +[ 51] 51 Code 0x000000000000b88c 0x0000000000000000 0x000e0000 powf_log2_synthesis +[ 52] 52 Code 0x000000000000b960 0x0000000000000000 0x000e0000 powf_log2_exactPowerOfTwo +[ 53] 53 Code 0x000000000000b980 0x0000000000000000 0x000e0000 powf_log2_near1 +[ 54] 54 Code 0x000000000000b9ec 0x0000000000000000 0x000e0000 powf_log2_synthesis_near1 +[ 55] 55 Code 0x000000000000ba04 0x0000000000000000 0x000e0000 Q32_minimax +[ 56] 56 Code 0x000000000000ba10 0x0000000000000000 0x000e0000 iexp2_lut +[ 57] 57 Code 0x000000000000ba94 0x0000000000000000 0x000e0000 powf_exp2 +[ 58] 58 Code 0x000000000000bb18 0x0000000000000000 0x000e0000 powf_exp2_exact_int +[ 59] 59 Code 0x000000000000bb24 0x0000000000000000 0x000e0000 powf_exp2_big +[ 60] 60 Code 0x000000000000bb74 0x0000000000000000 0x000e0000 powf_exp2_overflow diff --git a/utils/test/README-run-until-faulted b/utils/test/README-run-until-faulted new file mode 100644 index 000000000000..f89f8636d627 --- /dev/null +++ b/utils/test/README-run-until-faulted @@ -0,0 +1,18 @@ +A example usage of the Python script run-until-faulted.py: + +[18:20:29] johnny:/Volumes/data/lldb/svn/trunk/utils/test $ ./run-until-faulted.py -l /Volumes/data/lldb/svn/trunk/build/Debug/lldb -e './a.out' +lldb command: /Volumes/data/lldb/svn/trunk/build/Debug/lldb +executable: ./a.out +executable options: +sending file command.... +sending process launch -- (iteration: 0) + +* thread #1: tid = 0x2d03, 0x0000000100000eef a.out`main + 39 at main.c:7, stop reason = EXC_BAD_ACCESS (code=1, address=0x0) + 4 { + 5 int *null_ptr = 0; + 6 printf("Hello, fault!\n"); +-> 7 printf("Now segfault %d\n", *null_ptr); + 8 } + +(lldb) q +[18:20:40] johnny:/Volumes/data/lldb/svn/trunk/utils/test $ diff --git a/utils/test/disasm.py b/utils/test/disasm.py new file mode 100755 index 000000000000..46660299f188 --- /dev/null +++ b/utils/test/disasm.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +""" +Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, +and display the disassembly result. + +""" + +import os +import sys +from optparse import OptionParser + +def is_exe(fpath): + """Check whether fpath is an executable.""" + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def which(program): + """Find the full path to a program, or return None.""" + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +def do_llvm_mc_disassembly(gdb_commands, gdb_options, exe, func, mc, mc_options): + from cStringIO import StringIO + import pexpect + + gdb_prompt = "\r\n\(gdb\) " + gdb = pexpect.spawn(('gdb %s' % gdb_options) if gdb_options else 'gdb') + # Turn on logging for what gdb sends back. + gdb.logfile_read = sys.stdout + gdb.expect(gdb_prompt) + + # See if there any extra command(s) to execute before we issue the file command. + for cmd in gdb_commands: + gdb.sendline(cmd) + gdb.expect(gdb_prompt) + + # Now issue the file command. + gdb.sendline('file %s' % exe) + gdb.expect(gdb_prompt) + + # Send the disassemble command. + gdb.sendline('disassemble %s' % func) + gdb.expect(gdb_prompt) + + # Get the output from gdb. + gdb_output = gdb.before + + # Use StringIO to record the memory dump as well as the gdb assembler code. + mc_input = StringIO() + + # These keep track of the states of our simple gdb_output parser. + prev_line = None + prev_addr = None + curr_addr = None + addr_diff = 0 + looking = False + for line in gdb_output.split(os.linesep): + if line.startswith('Dump of assembler code'): + looking = True + continue + + if line.startswith('End of assembler dump.'): + looking = False + prev_addr = curr_addr + if mc_options and mc_options.find('arm') != -1: + addr_diff = 4 + if mc_options and mc_options.find('thumb') != -1: + # It is obviously wrong to assume the last instruction of the + # function has two bytes. + # FIXME + addr_diff = 2 + + if looking and line.startswith('0x'): + # It's an assembler code dump. + prev_addr = curr_addr + curr_addr = line.split(None, 1)[0] + if prev_addr and curr_addr: + addr_diff = int(curr_addr, 16) - int(prev_addr, 16) + + if prev_addr and addr_diff > 0: + # Feed the examining memory command to gdb. + gdb.sendline('x /%db %s' % (addr_diff, prev_addr)) + gdb.expect(gdb_prompt) + x_output = gdb.before + # Get the last output line from the gdb examine memory command, + # split the string into a 3-tuple with separator '>:' to handle + # objc method names. + memory_dump = x_output.split(os.linesep)[-1].partition('>:')[2].strip() + #print "\nbytes:", memory_dump + disasm_str = prev_line.partition('>:')[2] + print >> mc_input, '%s # %s' % (memory_dump, disasm_str) + + # We're done with the processing. Assign the current line to be prev_line. + prev_line = line + + # Close the gdb session now that we are done with it. + gdb.sendline('quit') + gdb.expect(pexpect.EOF) + gdb.close() + + # Write the memory dump into a file. + with open('disasm-input.txt', 'w') as f: + f.write(mc_input.getvalue()) + + mc_cmd = '%s -disassemble %s disasm-input.txt' % (mc, mc_options) + print "\nExecuting command:", mc_cmd + os.system(mc_cmd) + + # And invoke llvm-mc with the just recorded file. + #mc = pexpect.spawn('%s -disassemble %s disasm-input.txt' % (mc, mc_options)) + #mc.logfile_read = sys.stdout + #print "mc:", mc + #mc.close() + + +def main(): + # This is to set up the Python path to include the pexpect-2.4 dir. + # Remember to update this when/if things change. + scriptPath = sys.path[0] + sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) + + parser = OptionParser(usage="""\ +Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, +and display the disassembly result. + +Usage: %prog [options] +""") + parser.add_option('-C', '--gdb-command', + type='string', action='append', metavar='COMMAND', + default=[], dest='gdb_commands', + help='Command(s) gdb executes after starting up (can be empty)') + parser.add_option('-O', '--gdb-options', + type='string', action='store', + dest='gdb_options', + help="""The options passed to 'gdb' command if specified.""") + parser.add_option('-e', '--executable', + type='string', action='store', + dest='executable', + help="""The executable to do disassembly on.""") + parser.add_option('-f', '--function', + type='string', action='store', + dest='function', + help="""The function name (could be an address to gdb) for disassembly.""") + parser.add_option('-m', '--llvm-mc', + type='string', action='store', + dest='llvm_mc', + help="""The llvm-mc executable full path, if specified. + Otherwise, it must be present in your PATH environment.""") + + parser.add_option('-o', '--options', + type='string', action='store', + dest='llvm_mc_options', + help="""The options passed to 'llvm-mc -disassemble' command if specified.""") + + opts, args = parser.parse_args() + + gdb_commands = opts.gdb_commands + gdb_options = opts.gdb_options + + if not opts.executable: + parser.print_help() + sys.exit(1) + executable = opts.executable + + if not opts.function: + parser.print_help() + sys.exit(1) + function = opts.function + + llvm_mc = opts.llvm_mc if opts.llvm_mc else which('llvm-mc') + if not llvm_mc: + parser.print_help() + sys.exit(1) + + # This is optional. For example: + # --options='-triple=arm-apple-darwin -debug-only=arm-disassembler' + llvm_mc_options = opts.llvm_mc_options + + # We have parsed the options. + print "gdb commands:", gdb_commands + print "gdb options:", gdb_options + print "executable:", executable + print "function:", function + print "llvm-mc:", llvm_mc + print "llvm-mc options:", llvm_mc_options + + do_llvm_mc_disassembly(gdb_commands, gdb_options, executable, function, llvm_mc, llvm_mc_options) + +if __name__ == '__main__': + main() diff --git a/utils/test/lldb-disasm.py b/utils/test/lldb-disasm.py new file mode 100755 index 000000000000..7987c6b01c37 --- /dev/null +++ b/utils/test/lldb-disasm.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python + +""" +Run lldb to disassemble all the available functions for an executable image. + +""" + +import os +import re +import sys +from optparse import OptionParser + +def setupSysPath(): + """ + Add LLDB.framework/Resources/Python and the test dir to the sys.path. + """ + # Get the directory containing the current script. + scriptPath = sys.path[0] + if not scriptPath.endswith(os.path.join('utils', 'test')): + print "This script expects to reside in lldb's utils/test directory." + sys.exit(-1) + + # This is our base name component. + base = os.path.abspath(os.path.join(scriptPath, os.pardir, os.pardir)) + + # This is for the goodies in the test directory under base. + sys.path.append(os.path.join(base,'test')) + + # These are for xcode build directories. + xcode3_build_dir = ['build'] + xcode4_build_dir = ['build', 'lldb', 'Build', 'Products'] + dbg = ['Debug'] + rel = ['Release'] + bai = ['BuildAndIntegration'] + python_resource_dir = ['LLDB.framework', 'Resources', 'Python'] + + dbgPath = os.path.join(base, *(xcode3_build_dir + dbg + python_resource_dir)) + dbgPath2 = os.path.join(base, *(xcode4_build_dir + dbg + python_resource_dir)) + relPath = os.path.join(base, *(xcode3_build_dir + rel + python_resource_dir)) + relPath2 = os.path.join(base, *(xcode4_build_dir + rel + python_resource_dir)) + baiPath = os.path.join(base, *(xcode3_build_dir + bai + python_resource_dir)) + baiPath2 = os.path.join(base, *(xcode4_build_dir + bai + python_resource_dir)) + + lldbPath = None + if os.path.isfile(os.path.join(dbgPath, 'lldb.py')): + lldbPath = dbgPath + elif os.path.isfile(os.path.join(dbgPath2, 'lldb.py')): + lldbPath = dbgPath2 + elif os.path.isfile(os.path.join(relPath, 'lldb.py')): + lldbPath = relPath + elif os.path.isfile(os.path.join(relPath2, 'lldb.py')): + lldbPath = relPath2 + elif os.path.isfile(os.path.join(baiPath, 'lldb.py')): + lldbPath = baiPath + elif os.path.isfile(os.path.join(baiPath2, 'lldb.py')): + lldbPath = baiPath2 + + if not lldbPath: + print 'This script requires lldb.py to be in either ' + dbgPath + ',', + print relPath + ', or ' + baiPath + sys.exit(-1) + + # This is to locate the lldb.py module. Insert it right after sys.path[0]. + sys.path[1:1] = [lldbPath] + #print "sys.path:", sys.path + + +def run_command(ci, cmd, res, echo=True): + if echo: + print "run command:", cmd + ci.HandleCommand(cmd, res) + if res.Succeeded(): + if echo: + print "run_command output:", res.GetOutput() + else: + if echo: + print "run command failed!" + print "run_command error:", res.GetError() + +def do_lldb_disassembly(lldb_commands, exe, disassemble_options, num_symbols, + symbols_to_disassemble, + re_symbol_pattern, + quiet_disassembly): + import lldb, atexit, re + + # Create the debugger instance now. + dbg = lldb.SBDebugger.Create() + if not dbg: + raise Exception('Invalid debugger instance') + + # Register an exit callback. + atexit.register(lambda: lldb.SBDebugger.Terminate()) + + # We want our debugger to be synchronous. + dbg.SetAsync(False) + + # Get the command interpreter from the debugger. + ci = dbg.GetCommandInterpreter() + if not ci: + raise Exception('Could not get the command interpreter') + + # And the associated result object. + res = lldb.SBCommandReturnObject() + + # See if there any extra command(s) to execute before we issue the file command. + for cmd in lldb_commands: + run_command(ci, cmd, res, not quiet_disassembly) + + # Now issue the file command. + run_command(ci, 'file %s' % exe, res, not quiet_disassembly) + + # Create a target. + #target = dbg.CreateTarget(exe) + target = dbg.GetSelectedTarget() + stream = lldb.SBStream() + + def IsCodeType(symbol): + """Check whether an SBSymbol represents code.""" + return symbol.GetType() == lldb.eSymbolTypeCode + + # Define a generator for the symbols to disassemble. + def symbol_iter(num, symbols, re_symbol_pattern, target, verbose): + # If we specify the symbols to disassemble, ignore symbol table dump. + if symbols: + for i in range(len(symbols)): + if verbose: + print "symbol:", symbols[i] + yield symbols[i] + else: + limited = True if num != -1 else False + if limited: + count = 0 + if re_symbol_pattern: + pattern = re.compile(re_symbol_pattern) + stream = lldb.SBStream() + for m in target.module_iter(): + if verbose: + print "module:", m + for s in m: + if limited and count >= num: + return + # If a regexp symbol pattern is supplied, consult it. + if re_symbol_pattern: + # If the pattern does not match, look for the next symbol. + if not pattern.match(s.GetName()): + continue + + # If we come here, we're ready to disassemble the symbol. + if verbose: + print "symbol:", s.GetName() + if IsCodeType(s): + if limited: + count = count + 1 + if verbose: + print "returning symbol:", s.GetName() + yield s.GetName() + if verbose: + print "start address:", s.GetStartAddress() + print "end address:", s.GetEndAddress() + s.GetDescription(stream) + print "symbol description:", stream.GetData() + stream.Clear() + + # Disassembly time. + for symbol in symbol_iter(num_symbols, symbols_to_disassemble, re_symbol_pattern, target, not quiet_disassembly): + cmd = "disassemble %s '%s'" % (disassemble_options, symbol) + run_command(ci, cmd, res, not quiet_disassembly) + + +def main(): + # This is to set up the Python path to include the pexpect-2.4 dir. + # Remember to update this when/if things change. + scriptPath = sys.path[0] + sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) + + parser = OptionParser(usage="""\ +Run lldb to disassemble all the available functions for an executable image. + +Usage: %prog [options] +""") + parser.add_option('-C', '--lldb-command', + type='string', action='append', metavar='COMMAND', + default=[], dest='lldb_commands', + help='Command(s) lldb executes after starting up (can be empty)') + parser.add_option('-e', '--executable', + type='string', action='store', + dest='executable', + help="""Mandatory: the executable to do disassembly on.""") + parser.add_option('-o', '--options', + type='string', action='store', + dest='disassemble_options', + help="""Mandatory: the options passed to lldb's 'disassemble' command.""") + parser.add_option('-q', '--quiet-disassembly', + action='store_true', default=False, + dest='quiet_disassembly', + help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""") + parser.add_option('-n', '--num-symbols', + type='int', action='store', default=-1, + dest='num_symbols', + help="""The number of symbols to disassemble, if specified.""") + parser.add_option('-p', '--symbol_pattern', + type='string', action='store', + dest='re_symbol_pattern', + help="""The regular expression of symbols to invoke lldb's 'disassemble' command.""") + parser.add_option('-s', '--symbol', + type='string', action='append', metavar='SYMBOL', default=[], + dest='symbols_to_disassemble', + help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""") + + opts, args = parser.parse_args() + + lldb_commands = opts.lldb_commands + + if not opts.executable or not opts.disassemble_options: + parser.print_help() + sys.exit(1) + + executable = opts.executable + disassemble_options = opts.disassemble_options + quiet_disassembly = opts.quiet_disassembly + num_symbols = opts.num_symbols + symbols_to_disassemble = opts.symbols_to_disassemble + re_symbol_pattern = opts.re_symbol_pattern + + # We have parsed the options. + if not quiet_disassembly: + print "lldb commands:", lldb_commands + print "executable:", executable + print "disassemble options:", disassemble_options + print "quiet disassembly output:", quiet_disassembly + print "num of symbols to disassemble:", num_symbols + print "symbols to disassemble:", symbols_to_disassemble + print "regular expression of symbols to disassemble:", re_symbol_pattern + + setupSysPath() + do_lldb_disassembly(lldb_commands, executable, disassemble_options, + num_symbols, + symbols_to_disassemble, + re_symbol_pattern, + quiet_disassembly) + +if __name__ == '__main__': + main() diff --git a/utils/test/llvm-mc-shell.py b/utils/test/llvm-mc-shell.py new file mode 100755 index 000000000000..38c12992b914 --- /dev/null +++ b/utils/test/llvm-mc-shell.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +""" +Run llvm-mc interactively. + +""" + +import os +import sys +from optparse import OptionParser + +def is_exe(fpath): + """Check whether fpath is an executable.""" + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def which(program): + """Find the full path to a program, or return None.""" + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +def llvm_mc_loop(mc, mc_options): + contents = [] + fname = 'mc-input.txt' + sys.stdout.write("Enter your input to llvm-mc. A line starting with 'END' terminates the current batch of input.\n") + sys.stdout.write("Enter 'quit' or Ctrl-D to quit the program.\n") + while True: + sys.stdout.write("> ") + next = sys.stdin.readline() + # EOF => terminate this llvm-mc shell + if not next or next.startswith('quit'): + sys.stdout.write('\n') + sys.exit(0) + # 'END' => send the current batch of input to llvm-mc + if next.startswith('END'): + # Write contents to our file and clear the contents. + with open(fname, 'w') as f: + f.writelines(contents) + # Clear the list: replace all items with an empty list. + contents[:] = [] + + # Invoke llvm-mc with our newly created file. + mc_cmd = '%s %s %s' % (mc, mc_options, fname) + sys.stdout.write("Executing command: %s\n" % mc_cmd) + os.system(mc_cmd) + else: + # Keep accumulating our input. + contents.append(next) + +def main(): + # This is to set up the Python path to include the pexpect-2.4 dir. + # Remember to update this when/if things change. + scriptPath = sys.path[0] + sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) + + parser = OptionParser(usage="""\ +Do llvm-mc interactively within a shell-like environment. A batch of input is +submitted to llvm-mc to execute whenever you terminate the current batch by +inputing a line which starts with 'END'. Quit the program by either 'quit' or +Ctrl-D. + +Usage: %prog [options] +""") + parser.add_option('-m', '--llvm-mc', + type='string', action='store', + dest='llvm_mc', + help="""The llvm-mc executable full path, if specified. + Otherwise, it must be present in your PATH environment.""") + + parser.add_option('-o', '--options', + type='string', action='store', + dest='llvm_mc_options', + help="""The options passed to 'llvm-mc' command if specified.""") + + opts, args = parser.parse_args() + + llvm_mc = opts.llvm_mc if opts.llvm_mc else which('llvm-mc') + if not llvm_mc: + parser.print_help() + sys.exit(1) + + # This is optional. For example: + # --options='-disassemble -triple=arm-apple-darwin -debug-only=arm-disassembler' + llvm_mc_options = opts.llvm_mc_options + + # We have parsed the options. + print "llvm-mc:", llvm_mc + print "llvm-mc options:", llvm_mc_options + + llvm_mc_loop(llvm_mc, llvm_mc_options) + +if __name__ == '__main__': + main() diff --git a/utils/test/main.c b/utils/test/main.c new file mode 100644 index 000000000000..c0fe2f25a488 --- /dev/null +++ b/utils/test/main.c @@ -0,0 +1,14 @@ +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, const char* argv[]) +{ + int *null_ptr = 0; + printf("Hello, fault!\n"); + u_int32_t val = (arc4random() & 0x0f); + printf("val=%u\n", val); + if (val == 0x07) // Lucky 7 :-) + printf("Now segfault %d\n", *null_ptr); + else + printf("Better luck next time!\n"); +} diff --git a/utils/test/ras.py b/utils/test/ras.py new file mode 100755 index 000000000000..a89cbae8c88d --- /dev/null +++ b/utils/test/ras.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python + +""" +Run the test suite and send the result as an email message. + +The code for sending of the directory is copied from +http://docs.python.org/library/email-examples.html. +""" + +import os +import sys +import shutil +import smtplib +# For guessing MIME type based on file name extension +import mimetypes + +from optparse import OptionParser + +from email import encoders +from email.message import Message +from email.mime.audio import MIMEAudio +from email.mime.base import MIMEBase +from email.mime.image import MIMEImage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +def runTestsuite(testDir, sessDir, envs = None): + """Run the testsuite and return a (summary, output) tuple.""" + os.chdir(testDir) + + for env in envs: + list = env.split('=') + var = list[0].strip() + val = list[1].strip() + print var + "=" + val + os.environ[var] = val + + import shlex, subprocess + + command_line = "./dotest.py -w -s %s" % sessDir + # Apply correct tokenization for subprocess.Popen(). + args = shlex.split(command_line) + + # Use subprocess module to spawn a new process. + process = subprocess.Popen(args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Wait for subprocess to terminate. + stdout, stderr = process.communicate() + + # This will be used as the subject line of our email about this test. + cmd = "%s %s" % (' '.join(envs) if envs else "", command_line) + + return (cmd, stderr) + + +COMMASPACE = ', ' + +def main(): + parser = OptionParser(usage="""\ +Run lldb test suite and send the results as a MIME message. + +Usage: %prog [options] + +Unless the -o option is given, the email is sent by forwarding to the specified +SMTP server, which then does the normal delivery process. +""") + parser.add_option('-d', '--directory', + type='string', action='store', + dest='testDir', + help="""The LLDB test directory directly under the top dir. + Otherwise use the current directory.""") + # + # This is similar to TestBase.getRunSpec(self) from lldbtest.py. + # + parser.add_option('-e', '--environment', + type='string', action='append', metavar='ENVIRONMENT', + default=[], dest='environments', + help="""The environment setting as prefix to the test driver. + Example: -e 'CC=clang' -e 'ARCH=x86_64'""") + parser.add_option('-m', '--mailserver', + type='string', action='store', metavar='MAILSERVER', + dest='mailserver', + help="""The outgoing SMTP server.""") + parser.add_option('-o', '--output', + type='string', action='store', metavar='FILE', + help="""Print the composed message to FILE instead of + sending the message to the SMTP server.""") + parser.add_option('-s', '--sender', + type='string', action='store', metavar='SENDER', + help='The value of the From: header (required)') + parser.add_option('-r', '--recipient', + type='string', action='append', metavar='RECIPIENT', + default=[], dest='recipients', + help='A To: header value (at least one required)') + opts, args = parser.parse_args() + if not opts.sender or not opts.recipients: + parser.print_help() + sys.exit(1) + testDir = opts.testDir + if not testDir: + testDir = '.' + + sessDir = 'tmp-lldb-session' + if os.path.exists(sessDir): + shutil.rmtree(sessDir) + #print "environments:", opts.environments + summary, output = runTestsuite(testDir, sessDir, opts.environments) + + # Create the enclosing (outer) message + outer = MIMEMultipart() + outer['Subject'] = summary + outer['To'] = COMMASPACE.join(opts.recipients) + outer['From'] = opts.sender + outer.preamble = 'You will not see this in a MIME-aware mail reader.\n' + + # The sessDir contains all the session logs for failed/errored tests. + # Attach them all if it exists! + + if not os.path.exists(sessDir): + outer.attach(MIMEText(output, 'plain')) + else: + outer.attach(MIMEText("%s\n%s\n\n" % (output, + "Session logs of test failures/errors:"), + 'plain')) + + for filename in (os.listdir(sessDir) if os.path.exists(sessDir) else []): + path = os.path.join(sessDir, filename) + if not os.path.isfile(path): + continue + # Guess the content type based on the file's extension. Encoding + # will be ignored, although we should check for simple things like + # gzip'd or compressed files. + ctype, encoding = mimetypes.guess_type(path) + if ctype is None or encoding is not None: + # No guess could be made, or the file is encoded (compressed), so + # use a generic bag-of-bits type. + ctype = 'application/octet-stream' + maintype, subtype = ctype.split('/', 1) + if maintype == 'text': + fp = open(path) + # Note: we should handle calculating the charset + msg = MIMEText(fp.read(), _subtype=subtype) + fp.close() + elif maintype == 'image': + fp = open(path, 'rb') + msg = MIMEImage(fp.read(), _subtype=subtype) + fp.close() + elif maintype == 'audio': + fp = open(path, 'rb') + msg = MIMEAudio(fp.read(), _subtype=subtype) + fp.close() + else: + fp = open(path, 'rb') + msg = MIMEBase(maintype, subtype) + msg.set_payload(fp.read()) + fp.close() + # Encode the payload using Base64 + encoders.encode_base64(msg) + # Set the filename parameter + msg.add_header('Content-Disposition', 'attachment', filename=filename) + outer.attach(msg) + + # Now send or store the message + composed = outer.as_string() + if opts.output: + fp = open(opts.output, 'w') + fp.write(composed) + fp.close() + else: + s = smtplib.SMTP(opts.mailserver) + s.sendmail(opts.sender, opts.recipients, composed) + s.quit() + + +if __name__ == '__main__': + main() diff --git a/utils/test/run-dis.py b/utils/test/run-dis.py new file mode 100755 index 000000000000..a2437948e47d --- /dev/null +++ b/utils/test/run-dis.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +""" +Run lldb disassembler on all the binaries specified by a combination of root dir +and path pattern. +""" + +import os, sys, subprocess +import re +from optparse import OptionParser + +# The directory of this Python script as well as the lldb-disasm.py workhorse. +scriptPath = None + +# The root directory for the SDK symbols. +root_dir = None + +# The regular expression pattern to match the desired pathname to the binaries. +path_pattern = None + +# And the re-compiled regular expression object. +path_regexp = None + +# If specified, number of symbols to disassemble for each qualified binary. +num_symbols = -1 + +# Command template of the invocation of lldb disassembler. +template = '%s/lldb-disasm.py -C "platform select remote-ios" -o "-n" -q -e %s -n %s' + +# Regular expression for detecting file output for Mach-o binary. +mach_o = re.compile('\sMach-O.+binary') +def isbinary(path): + file_output = subprocess.Popen(["file", path], + stdout=subprocess.PIPE).stdout.read() + return (mach_o.search(file_output) is not None) + +def walk_and_invoke(sdk_root, path_regexp, suffix, num_symbols): + """Look for matched file and invoke lldb disassembly on it.""" + global scriptPath + + for root, dirs, files in os.walk(sdk_root, topdown=False): + for name in files: + path = os.path.join(root, name) + + # We're not interested in .h file. + if name.endswith(".h"): + continue + # Neither a symbolically linked file. + if os.path.islink(path): + continue + + # We'll be pattern matching based on the path relative to the SDK root. + replaced_path = path.replace(root_dir, "", 1) + # Check regular expression match for the replaced path. + if not path_regexp.search(replaced_path): + continue + # If a suffix is specified, check it, too. + if suffix and not name.endswith(suffix): + continue + if not isbinary(path): + continue + + command = template % (scriptPath, path, num_symbols if num_symbols > 0 else 1000) + print "Running %s" % (command) + os.system(command) + +def main(): + """Read the root dir and the path spec, invoke lldb-disasm.py on the file.""" + global scriptPath + global root_dir + global path_pattern + global path_regexp + global num_symbols + + scriptPath = sys.path[0] + + parser = OptionParser(usage="""\ +Run lldb disassembler on all the binaries specified by a combination of root dir +and path pattern. +""") + parser.add_option('-r', '--root-dir', + type='string', action='store', + dest='root_dir', + help='Mandatory: the root directory for the SDK symbols.') + parser.add_option('-p', '--path-pattern', + type='string', action='store', + dest='path_pattern', + help='Mandatory: regular expression pattern for the desired binaries.') + parser.add_option('-s', '--suffix', + type='string', action='store', default=None, + dest='suffix', + help='Specify the suffix of the binaries to look for.') + parser.add_option('-n', '--num-symbols', + type='int', action='store', default=-1, + dest='num_symbols', + help="""The number of symbols to disassemble, if specified.""") + + + opts, args = parser.parse_args() + if not opts.root_dir or not opts.path_pattern: + parser.print_help() + sys.exit(1) + + # Sanity check the root directory. + root_dir = opts.root_dir + root_dir = os.path.abspath(root_dir) + if not os.path.isdir(root_dir): + parser.print_help() + sys.exit(1) + + path_pattern = opts.path_pattern + path_regexp = re.compile(path_pattern) + suffix = opts.suffix + num_symbols = opts.num_symbols + + print "Root directory for SDK symbols:", root_dir + print "Regular expression for the binaries:", path_pattern + print "Suffix of the binaries to look for:", suffix + print "num of symbols to disassemble:", num_symbols + + walk_and_invoke(root_dir, path_regexp, suffix, num_symbols) + + +if __name__ == '__main__': + main() diff --git a/utils/test/run-until-faulted.py b/utils/test/run-until-faulted.py new file mode 100755 index 000000000000..d895f565a79e --- /dev/null +++ b/utils/test/run-until-faulted.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +""" +Run a program via lldb until it fails. +The lldb executable is located via your PATH env variable, if not specified. +""" + +import os +import sys +from optparse import OptionParser + +def is_exe(fpath): + """Check whether fpath is an executable.""" + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def which(program): + """Find the full path to a program, or return None.""" + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +def do_lldb_launch_loop(lldb_command, exe, exe_options): + from cStringIO import StringIO + import pexpect, time + + prompt = "\(lldb\) " + lldb = pexpect.spawn(lldb_command) + # Turn on logging for what lldb sends back. + lldb.logfile_read = sys.stdout + lldb.expect(prompt) + + # Now issue the file command. + #print "sending 'file %s' command..." % exe + lldb.sendline('file %s' % exe) + lldb.expect(prompt) + + # Loop until it faults.... + count = 0 + #while True: + # count = count + 1 + for i in range(100): + count = i + #print "sending 'process launch -- %s' command... (iteration: %d)" % (exe_options, count) + lldb.sendline('process launch -- %s' % exe_options) + index = lldb.expect(['Process .* exited with status', + 'Process .* stopped', + pexpect.TIMEOUT]) + if index == 0: + # We'll try again later. + time.sleep(3) + elif index == 1: + # Perfect, our process had stopped; break out of the loop. + break; + elif index == 2: + # Something went wrong. + print "TIMEOUT occurred:", str(lldb) + + # Give control of lldb shell to the user. + lldb.interact() + +def main(): + # This is to set up the Python path to include the pexpect-2.4 dir. + # Remember to update this when/if things change. + scriptPath = sys.path[0] + sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) + + parser = OptionParser(usage="""\ +%prog [options] +Run a program via lldb until it fails. +The lldb executable is located via your PATH env variable, if not specified.\ +""") + parser.add_option('-l', '--lldb-command', + type='string', action='store', metavar='LLDB_COMMAND', + default='lldb', dest='lldb_command', + help='Full path to your lldb command') + parser.add_option('-e', '--executable', + type='string', action='store', + dest='exe', + help="""(Mandatory) The executable to launch via lldb.""") + parser.add_option('-o', '--options', + type='string', action='store', + default = '', dest='exe_options', + help="""The args/options passed to the launched program, if specified.""") + + opts, args = parser.parse_args() + + lldb_command = which(opts.lldb_command) + + if not opts.exe: + parser.print_help() + sys.exit(1) + exe = opts.exe + + exe_options = opts.exe_options + + # We have parsed the options. + print "lldb command:", lldb_command + print "executable:", exe + print "executable options:", exe_options + + do_lldb_launch_loop(lldb_command, exe, exe_options) + +if __name__ == '__main__': + main() diff --git a/utils/vim-lldb/README b/utils/vim-lldb/README new file mode 100644 index 000000000000..721054a2c84b --- /dev/null +++ b/utils/vim-lldb/README @@ -0,0 +1,59 @@ + +================= +LLDB Vim Frontend +================= + +Prerequisites +------------- + +This plugin is known to work with the following flavours of Vim: + + * Linux (tested on Ubuntu 12.04/12.10): + * vim/gvim (from vim-gnome package version 7.3) + + * Mac OS X (tested on Mountain Lion) + * Vim command-line (7.3 from Xcode) + * MacVim 7.3 + +To install the plugin, ensure you have + * a working version of lldb on your path, or the environment variable LLDB + pointing to the lldb binary you would like to use. + * a python-enabled vim (check with ":python print 2") + + +Installation +------------ + +1) Install the Vim pathogen plugin (it keeps installed plugins organized): + + https://github.com/tpope/vim-pathogen + + Or, for the impatient: + +mkdir -p ~/.vim/autoload ~/.vim/bundle; \ +curl -Sso ~/.vim/autoload/pathogen.vim \ + https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim + +2) Symlink (or copy) ~/.vim/bundle/vim-lldb to this directory: + +ln -sf <lldb-dir>/utils/vim-lldb ~/.vim/bundle/vim-lldb + +3) Update your help-tags. Start vim, do: + + :Helptags + +4) Have fun! + + +Usage/Getting Help +------------------ +All LLDB commands (with tab-completion) can be accessed in Vim's +command mode. Try it out by typing: + +:L<tab> + +There are several sources of help available: + +:help lldb -- Documentation for this plugin +:Lhelp -- LLDB's built-in help system (i.e lldb 'help' command) +:Lscript help (lldb) -- Complete LLDB Python API reference diff --git a/utils/vim-lldb/doc/lldb.txt b/utils/vim-lldb/doc/lldb.txt new file mode 100644 index 000000000000..e54e6f2db0dc --- /dev/null +++ b/utils/vim-lldb/doc/lldb.txt @@ -0,0 +1,115 @@ +*lldb.txt* A plugin that enables debugging from your favourite editor + +Author: Daniel Malea <daniel.malea@intel.com> +License: Same terms as Vim itself (see |license|) + +INTRODUCTION *lldb* + +Installing this plugin enables a set of commands in Vim to control the +LLDB (http://lldb.llvm.org) debugger. + +COMMANDS *lldb-commands* + +The LLDB command interpreter is exposed to Vim's command mode using the +':L' prefix. Tab-completion is available and will cycle through commands. +Some commands have modified behaviour in Vim; for example, :Lbreakpoint +with no arguments will set a breakpoint at the current cursor, rather than +printing the standard help information for the LLDB command 'breakpoint'. + + *lldb-windows* + +In addition to the standard commands available under the LLDB interpreter, +there are also commands to display or hide informational debugger panes. + +Windows can be shown or hidden using the ':Lhide <name>' or ':Lshow <name>' +commands. + *lldb-:Lhide* +:Lhide [windowname] Hide informational debugger pane named 'windowname'. + + *lldb-:Lshow* +:Lshow [windowname] Show informational debugger pane named 'windowname'. + +Possible window name arguments to the Lhide and Lshow commands include: + + * backtrace + * breakpoints + * disassembly + * locals + * registers + * threads + *lldb-:Lattach* +:Lattach <process-name> Attach to a process by name. + + *lldb-:Ldetach* +:Ldetach Detach from the current process. + + *lldb-:Ltarget* +:Ltarget [[create] executable] + Create a target with the specified executable. If + run with a single argument, that argument is assumed + to be a path to the executable to be debugged. + Otherwise, all arguments are passed into LLDB's command + interpreter. + + *lldb-:Lstart* +:Lstart Create a process by executing the current target + and wait for LLDB to attach. + + *lldb-:Lrun* +:Lrun Create a process by executing the current target + without waiting for LLDB to attach. + + *lldb-:Lcontinue* +:Lcontinue Continue execution of the process until the next + breakpoint is hit or the process exits. + + *lldb-:Lthread* +:Lthread <args> Passes through to LLDB. See :Lhelp thread. + + *lldb-:Lstep* +:Lstep Step into the current function call. + + *lldb-:Lstepin* +:Lstepin Step into the current function call. + + *lldb-:Lstepinst* +:Lstepinst Step one instruction. + + *lldb-:Lstepinstover* +:Lstepinstover Step one instruction, but skip over jump or call + instructions. + + *lldb-:Lnext* +:Lnext Step to the next line. + + *lldb-:Lfinish* +:Lfinish Step out of the current function. + + *lldb-:Lbreakpoint* +:Lbreakpoint [args] When arguments are provided, the lldb breakpoint + command is invoked. If no arguments are provided, + a breakpoint at the location under the cursor. + + *lldb-:Lprint* + *lldb-:Lpo* + *lldb-:LpO* +:Lprint <expr> Aliases to the lldb print and po commands. Cursor +:Lpo <expr> word (cursor WORD for LpO) will be used when +:LpO <expr> expression omitted. + +MAPPINGS *lldb-mappings* + +On Mac OS X (under MacVim) , the following key mappings are available: + +<Command-B> Insert a breakpoint at the line under cursor + + +ABOUT *lldb-about* + +Grab the latest version of this plugin (and LLDB sources) with: + git clone http://llvm.org/git/lldb + +File any bugs at: + http://llvm.org/bugs/enter_bug.cgi?product=lldb + + vim:tw=78:et:ft=help:norl: diff --git a/utils/vim-lldb/plugin/lldb.vim b/utils/vim-lldb/plugin/lldb.vim new file mode 100644 index 000000000000..ac5cfe3fbb43 --- /dev/null +++ b/utils/vim-lldb/plugin/lldb.vim @@ -0,0 +1,151 @@ + +" Vim script glue code for LLDB integration + +function! s:FindPythonScriptDir() + for dir in pathogen#split(&runtimepath) + let searchstr = "python-vim-lldb" + let candidates = pathogen#glob_directories(dir . "/" . searchstr) + if len(candidates) > 0 + return candidates[0] + endif + endfor + return +endfunction() + +function! s:InitLldbPlugin() + if has('python') == 0 + call confirm('ERROR: This Vim installation does not have python support. lldb.vim will not work.') + return + endif + + " Key-Bindings + " FIXME: choose sensible keybindings for: + " - process: start, interrupt, continue, continue-to-cursor + " - step: instruction, in, over, out + " + if has('gui_macvim') + " Apple-B toggles breakpoint on cursor + map <D-B> :Lbreakpoint<CR> + endif + + " + " Setup the python interpreter path + " + let vim_lldb_pydir = s:FindPythonScriptDir() + execute 'python import sys; sys.path.append("' . vim_lldb_pydir . '")' + + " + " Register :L<Command> + " The LLDB CommandInterpreter provides tab-completion in Vim's command mode. + " FIXME: this list of commands, at least partially should be auto-generated + " + + " Window show/hide commands + command -complete=custom,s:CompleteWindow -nargs=1 Lhide python ctrl.doHide('<args>') + command -complete=custom,s:CompleteWindow -nargs=0 Lshow python ctrl.doShow('<args>') + + " Launching convenience commands (no autocompletion) + command -nargs=* Lstart python ctrl.doLaunch(True, '<args>') + command -nargs=* Lrun python ctrl.doLaunch(False, '<args>') + command -nargs=1 Lattach python ctrl.doAttach('<args>') + command -nargs=0 Ldetach python ctrl.doDetach() + + " Regexp-commands: because vim's command mode does not support '_' or '-' + " characters in command names, we omit them when creating the :L<cmd> + " equivalents. + command -complete=custom,s:CompleteCommand -nargs=* Lregexpattach python ctrl.doCommand('_regexp-attach', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpbreak python ctrl.doCommand('_regexp-break', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpbt python ctrl.doCommand('_regexp-bt', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpdown python ctrl.doCommand('_regexp-down', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexptbreak python ctrl.doCommand('_regexp-tbreak', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpdisplay python ctrl.doCommand('_regexp-display', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpundisplay python ctrl.doCommand('_regexp-undisplay', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpup python ctrl.doCommand('_regexp-up', '<args>') + + command -complete=custom,s:CompleteCommand -nargs=* Lapropos python ctrl.doCommand('apropos', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lbacktrace python ctrl.doCommand('bt', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lbreakpoint python ctrl.doBreakpoint('<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lcommand python ctrl.doCommand('command', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Ldisassemble python ctrl.doCommand('disassemble', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lexpression python ctrl.doCommand('expression', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lhelp python ctrl.doCommand('help', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Llog python ctrl.doCommand('log', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lplatform python ctrl.doCommand('platform','<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lplugin python ctrl.doCommand('plugin', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lprocess python ctrl.doProcess('<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lregister python ctrl.doCommand('register', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lscript python ctrl.doCommand('script', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lsettings python ctrl.doCommand('settings','<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lsource python ctrl.doCommand('source', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Ltype python ctrl.doCommand('type', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lversion python ctrl.doCommand('version', '<args>') + command -complete=custom,s:CompleteCommand -nargs=* Lwatchpoint python ctrl.doCommand('watchpoint', '<args>') + + " Convenience (shortcut) LLDB commands + command -complete=custom,s:CompleteCommand -nargs=* Lprint python ctrl.doCommand('print', vim.eval("s:CursorWord('<args>')")) + command -complete=custom,s:CompleteCommand -nargs=* Lpo python ctrl.doCommand('po', vim.eval("s:CursorWord('<args>')")) + command -complete=custom,s:CompleteCommand -nargs=* LpO python ctrl.doCommand('po', vim.eval("s:CursorWORD('<args>')")) + command -complete=custom,s:CompleteCommand -nargs=* Lbt python ctrl.doCommand('bt', '<args>') + + " Frame/Thread-Selection (commands that also do an Uupdate but do not + " generate events in LLDB) + command -complete=custom,s:CompleteCommand -nargs=* Lframe python ctrl.doSelect('frame', '<args>') + command -complete=custom,s:CompleteCommand -nargs=? Lup python ctrl.doCommand('up', '<args>', print_on_success=False, goto_file=True) + command -complete=custom,s:CompleteCommand -nargs=? Ldown python ctrl.doCommand('down', '<args>', print_on_success=False, goto_file=True) + command -complete=custom,s:CompleteCommand -nargs=* Lthread python ctrl.doSelect('thread', '<args>') + + command -complete=custom,s:CompleteCommand -nargs=* Ltarget python ctrl.doTarget('<args>') + + " Continue + command -complete=custom,s:CompleteCommand -nargs=* Lcontinue python ctrl.doContinue() + + " Thread-Stepping (no autocompletion) + command -nargs=0 Lstepinst python ctrl.doStep(StepType.INSTRUCTION) + command -nargs=0 Lstepinstover python ctrl.doStep(StepType.INSTRUCTION_OVER) + command -nargs=0 Lstepin python ctrl.doStep(StepType.INTO) + command -nargs=0 Lstep python ctrl.doStep(StepType.INTO) + command -nargs=0 Lnext python ctrl.doStep(StepType.OVER) + command -nargs=0 Lfinish python ctrl.doStep(StepType.OUT) + + " hack: service the LLDB event-queue when the cursor moves + " FIXME: some threaded solution would be better...but it + " would have to be designed carefully because Vim's APIs are non threadsafe; + " use of the vim module **MUST** be restricted to the main thread. + command -nargs=0 Lrefresh python ctrl.doRefresh() + autocmd CursorMoved * :Lrefresh + autocmd CursorHold * :Lrefresh + autocmd VimLeavePre * python ctrl.doExit() + + execute 'pyfile ' . vim_lldb_pydir . '/plugin.py' +endfunction() + +function! s:CompleteCommand(A, L, P) + python << EOF +a = vim.eval("a:A") +l = vim.eval("a:L") +p = vim.eval("a:P") +returnCompleteCommand(a, l, p) +EOF +endfunction() + +function! s:CompleteWindow(A, L, P) + python << EOF +a = vim.eval("a:A") +l = vim.eval("a:L") +p = vim.eval("a:P") +returnCompleteWindow(a, l, p) +EOF +endfunction() + +" Returns cword if search term is empty +function! s:CursorWord(term) + return empty(a:term) ? expand('<cword>') : a:term +endfunction() + +" Returns cleaned cWORD if search term is empty +function! s:CursorWORD(term) + " Will strip all non-alphabetic characters from both sides + return empty(a:term) ? substitute(expand('<cWORD>'), '^\A*\(.\{-}\)\A*$', '\1', '') : a:term +endfunction() + +call s:InitLldbPlugin() diff --git a/utils/vim-lldb/python-vim-lldb/import_lldb.py b/utils/vim-lldb/python-vim-lldb/import_lldb.py new file mode 100644 index 000000000000..a2145d504661 --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/import_lldb.py @@ -0,0 +1,61 @@ + +# Locate and load the lldb python module + +import os, sys + +def import_lldb(): + """ Find and import the lldb modules. This function tries to find the lldb module by: + 1. Simply by doing "import lldb" in case the system python installation is aware of lldb. If that fails, + 2. Executes the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb + on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid + path, it is added to sys.path and the import is attempted again. If that fails, 3. On Mac OS X the + default Xcode 4.5 installation path. + """ + + # Try simple 'import lldb', in case of a system-wide install or a pre-configured PYTHONPATH + try: + import lldb + return True + except ImportError: + pass + + # Allow overriding default path to lldb executable with the LLDB environment variable + lldb_executable = 'lldb' + if 'LLDB' in os.environ and os.path.exists(os.environ['LLDB']): + lldb_executable = os.environ['LLDB'] + + # Try using builtin module location support ('lldb -P') + from subprocess import check_output, CalledProcessError + try: + with open(os.devnull, 'w') as fnull: + lldb_minus_p_path = check_output("%s -P" % lldb_executable, shell=True, stderr=fnull).strip() + if not os.path.exists(lldb_minus_p_path): + #lldb -P returned invalid path, probably too old + pass + else: + sys.path.append(lldb_minus_p_path) + import lldb + return True + except CalledProcessError: + # Cannot run 'lldb -P' to determine location of lldb python module + pass + except ImportError: + # Unable to import lldb module from path returned by `lldb -P` + pass + + # On Mac OS X, use the try the default path to XCode lldb module + if "darwin" in sys.platform: + xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python/" + sys.path.append(xcode_python_path) + try: + import lldb + return True + except ImportError: + # Unable to import lldb module from default Xcode python path + pass + + return False + +if not import_lldb(): + import vim + vim.command('redraw | echo "%s"' % " Error loading lldb module; vim-lldb will be disabled. Check LLDB installation or set LLDB environment variable.") diff --git a/utils/vim-lldb/python-vim-lldb/lldb_controller.py b/utils/vim-lldb/python-vim-lldb/lldb_controller.py new file mode 100644 index 000000000000..923e771e6afe --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/lldb_controller.py @@ -0,0 +1,384 @@ + +# +# This file defines the layer that talks to lldb +# + +import os, re, sys +import lldb +import vim +from vim_ui import UI + +# ================================================= +# Convert some enum value to its string counterpart +# ================================================= + +# 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() + 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)) + +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)) + +global ctrl +ctrl = LLDBController() diff --git a/utils/vim-lldb/python-vim-lldb/plugin.py b/utils/vim-lldb/python-vim-lldb/plugin.py new file mode 100644 index 000000000000..694783a95b0e --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/plugin.py @@ -0,0 +1,14 @@ + +# Try to import all dependencies, catch and handle the error gracefully if it fails. + +import import_lldb + +try: + import lldb + import vim +except ImportError: + 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") + pass +else: + # Everthing went well, so use import to start the plugin controller + from lldb_controller import * diff --git a/utils/vim-lldb/python-vim-lldb/vim_panes.py b/utils/vim-lldb/python-vim-lldb/vim_panes.py new file mode 100644 index 000000000000..ec537199922c --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/vim_panes.py @@ -0,0 +1,618 @@ +# +# This file contains implementations of the LLDB display panes in VIM +# +# The most generic way to define a new window is to inherit from VimPane +# and to implement: +# - get_content() - returns a string with the pane contents +# +# Optionally, to highlight text, implement: +# - get_highlights() - returns a map +# +# And call: +# - define_highlight(unique_name, colour) +# at some point in the constructor. +# +# +# If the pane shows some key-value data that is in the context of a +# single frame, inherit from FrameKeyValuePane and implement: +# - get_frame_content(self, SBFrame frame) +# +# +# If the pane presents some information that can be retrieved with +# a simple LLDB command while the subprocess is stopped, inherit +# from StoppedCommandPane and call: +# - self.setCommand(command, command_args) +# at some point in the constructor. +# +# Optionally, you can implement: +# - get_selected_line() +# to highlight a selected line and place the cursor there. +# +# +# FIXME: implement WatchlistPane to displayed watched expressions +# FIXME: define interface for interactive panes, like catching enter +# presses to change selected frame/thread... +# + +import lldb +import vim + +import sys + +# ============================================================== +# Get the description of an lldb object or None if not available +# ============================================================== + +# Shamelessly copy/pasted from lldbutil.py in the test suite +def get_description(obj, option=None): + """Calls lldb_obj.GetDescription() and returns a string, or None. + + For SBTarget, SBBreakpointLocation, and SBWatchpoint lldb objects, an extra + option can be passed in to describe the detailed level of description + desired: + o lldb.eDescriptionLevelBrief + o lldb.eDescriptionLevelFull + o lldb.eDescriptionLevelVerbose + """ + method = getattr(obj, 'GetDescription') + if not method: + return None + tuple = (lldb.SBTarget, lldb.SBBreakpointLocation, lldb.SBWatchpoint) + if isinstance(obj, tuple): + if option is None: + option = lldb.eDescriptionLevelBrief + + stream = lldb.SBStream() + if option is None: + success = method(stream) + else: + success = method(stream, option) + if not success: + return None + return stream.GetData() + +def get_selected_thread(target): + """ Returns a tuple with (thread, error) where thread == None if error occurs """ + process = target.GetProcess() + if process is None or not process.IsValid(): + return (None, VimPane.MSG_NO_PROCESS) + + thread = process.GetSelectedThread() + if thread is None or not thread.IsValid(): + return (None, VimPane.MSG_NO_THREADS) + return (thread, "") + +def get_selected_frame(target): + """ Returns a tuple with (frame, error) where frame == None if error occurs """ + (thread, error) = get_selected_thread(target) + if thread is None: + return (None, error) + + frame = thread.GetSelectedFrame() + if frame is None or not frame.IsValid(): + return (None, VimPane.MSG_NO_FRAME) + return (frame, "") + +def _cmd(cmd): + vim.command("call confirm('%s')" % cmd) + vim.command(cmd) + +def move_cursor(line, col=0): + """ moves cursor to specified line and col """ + cw = vim.current.window + if cw.cursor[0] != line: + vim.command("execute \"normal %dgg\"" % line) + +def winnr(): + """ Returns currently selected window number """ + return int(vim.eval("winnr()")) + +def bufwinnr(name): + """ Returns window number corresponding with buffer name """ + return int(vim.eval("bufwinnr('%s')" % name)) + +def goto_window(nr): + """ go to window number nr""" + if nr != winnr(): + vim.command(str(nr) + ' wincmd w') + +def goto_next_window(): + """ go to next window. """ + vim.command('wincmd w') + return (winnr(), vim.current.buffer.name) + +def goto_previous_window(): + """ go to previously selected window """ + vim.command("execute \"normal \\<c-w>p\"") + +def have_gui(): + """ Returns True if vim is in a gui (Gvim/MacVim), False otherwise. """ + return int(vim.eval("has('gui_running')")) == 1 + +class PaneLayout(object): + """ A container for a (vertical) group layout of VimPanes """ + + def __init__(self): + self.panes = {} + + def havePane(self, name): + """ Returns true if name is a registered pane, False otherwise """ + return name in self.panes + + def prepare(self, panes = []): + """ Draw panes on screen. If empty list is provided, show all. """ + + # If we can't select a window contained in the layout, we are doing a first draw + first_draw = not self.selectWindow(True) + did_first_draw = False + + # Prepare each registered pane + for name in self.panes: + if name in panes or len(panes) == 0: + if first_draw: + # First window in layout will be created with :vsp, and closed later + vim.command(":vsp") + first_draw = False + did_first_draw = True + self.panes[name].prepare() + + if did_first_draw: + # Close the split window + vim.command(":q") + + self.selectWindow(False) + + def contains(self, bufferName = None): + """ Returns True if window with name bufferName is contained in the layout, False otherwise. + If bufferName is None, the currently selected window is checked. + """ + if not bufferName: + bufferName = vim.current.buffer.name + + for p in self.panes: + if bufferName is not None and bufferName.endswith(p): + return True + return False + + def selectWindow(self, select_contained = True): + """ Selects a window contained in the layout (if select_contained = True) and returns True. + If select_contained = False, a window that is not contained is selected. Returns False + if no group windows can be selected. + """ + if select_contained == self.contains(): + # Simple case: we are already selected + return True + + # Otherwise, switch to next window until we find a contained window, or reach the first window again. + first = winnr() + (curnum, curname) = goto_next_window() + + while not select_contained == self.contains(curname) and curnum != first: + (curnum, curname) = goto_next_window() + + return self.contains(curname) == select_contained + + def hide(self, panes = []): + """ Hide panes specified. If empty list provided, hide all. """ + for name in self.panes: + if name in panes or len(panes) == 0: + self.panes[name].destroy() + + def registerForUpdates(self, p): + self.panes[p.name] = p + + def update(self, target, controller): + for name in self.panes: + self.panes[name].update(target, controller) + + +class VimPane(object): + """ A generic base class for a pane that displays stuff """ + CHANGED_VALUE_HIGHLIGHT_NAME_GUI = 'ColorColumn' + CHANGED_VALUE_HIGHLIGHT_NAME_TERM = 'lldb_changed' + CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM = 'darkred' + + SELECTED_HIGHLIGHT_NAME_GUI = 'Cursor' + SELECTED_HIGHLIGHT_NAME_TERM = 'lldb_selected' + SELECTED_HIGHLIGHT_COLOUR_TERM = 'darkblue' + + MSG_NO_TARGET = "Target does not exist." + MSG_NO_PROCESS = "Process does not exist." + MSG_NO_THREADS = "No valid threads." + MSG_NO_FRAME = "No valid frame." + + # list of defined highlights, so we avoid re-defining them + highlightTypes = [] + + def __init__(self, owner, name, open_below=False, height=3): + self.owner = owner + self.name = name + self.buffer = None + self.maxHeight = 20 + self.openBelow = open_below + self.height = height + self.owner.registerForUpdates(self) + + def isPrepared(self): + """ check window is OK """ + if self.buffer == None or len(dir(self.buffer)) == 0 or bufwinnr(self.name) == -1: + return False + return True + + def prepare(self, method = 'new'): + """ check window is OK, if not then create """ + if not self.isPrepared(): + self.create(method) + + def on_create(self): + pass + + def destroy(self): + """ destroy window """ + if self.buffer == None or len(dir(self.buffer)) == 0: + return + vim.command('bdelete ' + self.name) + + def create(self, method): + """ create window """ + + if method != 'edit': + belowcmd = "below" if self.openBelow else "" + vim.command('silent %s %s %s' % (belowcmd, method, self.name)) + else: + vim.command('silent %s %s' % (method, self.name)) + + self.window = vim.current.window + + # Set LLDB pane options + vim.command("setlocal buftype=nofile") # Don't try to open a file + vim.command("setlocal noswapfile") # Don't use a swap file + vim.command("set nonumber") # Don't display line numbers + #vim.command("set nowrap") # Don't wrap text + + # Save some parameters and reference to buffer + self.buffer = vim.current.buffer + self.width = int( vim.eval("winwidth(0)") ) + self.height = int( vim.eval("winheight(0)") ) + + self.on_create() + goto_previous_window() + + def update(self, target, controller): + """ updates buffer contents """ + self.target = target + if not self.isPrepared(): + # Window is hidden, or otherwise not ready for an update + return + + original_cursor = self.window.cursor + + # Select pane + goto_window(bufwinnr(self.name)) + + # Clean and update content, and apply any highlights. + self.clean() + + if self.write(self.get_content(target, controller)): + self.apply_highlights() + + cursor = self.get_selected_line() + if cursor is None: + # Place the cursor at its original position in the window + cursor_line = min(original_cursor[0], len(self.buffer)) + cursor_col = min(original_cursor[1], len(self.buffer[cursor_line - 1])) + else: + # Place the cursor at the location requested by a VimPane implementation + cursor_line = min(cursor, len(self.buffer)) + cursor_col = self.window.cursor[1] + + self.window.cursor = (cursor_line, cursor_col) + + goto_previous_window() + + def get_selected_line(self): + """ Returns the line number to move the cursor to, or None to leave + it where the user last left it. + Subclasses implement this to define custom behaviour. + """ + return None + + def apply_highlights(self): + """ Highlights each set of lines in each highlight group """ + highlights = self.get_highlights() + for highlightType in highlights: + lines = highlights[highlightType] + if len(lines) == 0: + continue + + cmd = 'match %s /' % highlightType + lines = ['\%' + '%d' % line + 'l' for line in lines] + cmd += '\\|'.join(lines) + cmd += '/' + vim.command(cmd) + + def define_highlight(self, name, colour): + """ Defines highlihght """ + if name in VimPane.highlightTypes: + # highlight already defined + return + + vim.command("highlight %s ctermbg=%s guibg=%s" % (name, colour, colour)) + VimPane.highlightTypes.append(name) + + def write(self, msg): + """ replace buffer with msg""" + self.prepare() + + msg = str(msg.encode("utf-8", "replace")).split('\n') + try: + self.buffer.append(msg) + vim.command("execute \"normal ggdd\"") + except vim.error: + # cannot update window; happens when vim is exiting. + return False + + move_cursor(1, 0) + return True + + def clean(self): + """ clean all datas in buffer """ + self.prepare() + vim.command(':%d') + #self.buffer[:] = None + + def get_content(self, target, controller): + """ subclasses implement this to provide pane content """ + assert(0 and "pane subclass must implement this") + pass + + def get_highlights(self): + """ Subclasses implement this to provide pane highlights. + This function is expected to return a map of: + { highlight_name ==> [line_number, ...], ... } + """ + return {} + + +class FrameKeyValuePane(VimPane): + def __init__(self, owner, name, open_below): + """ Initialize parent, define member variables, choose which highlight + to use based on whether or not we have a gui (MacVim/Gvim). + """ + + VimPane.__init__(self, owner, name, open_below) + + # Map-of-maps key/value history { frame --> { variable_name, variable_value } } + self.frameValues = {} + + if have_gui(): + self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_GUI + else: + self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM + self.define_highlight(VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM, + VimPane.CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM) + + def format_pair(self, key, value, changed = False): + """ Formats a key/value pair. Appends a '*' if changed == True """ + marker = '*' if changed else ' ' + return "%s %s = %s\n" % (marker, key, value) + + def get_content(self, target, controller): + """ Get content for a frame-aware pane. Also builds the list of lines that + need highlighting (i.e. changed values.) + """ + if target is None or not target.IsValid(): + return VimPane.MSG_NO_TARGET + + self.changedLines = [] + + (frame, err) = get_selected_frame(target) + if frame is None: + return err + + output = get_description(frame) + lineNum = 1 + + # Retrieve the last values displayed for this frame + frameId = get_description(frame.GetBlock()) + if frameId in self.frameValues: + frameOldValues = self.frameValues[frameId] + else: + frameOldValues = {} + + # Read the frame variables + vals = self.get_frame_content(frame) + for (key, value) in vals: + lineNum += 1 + if len(frameOldValues) == 0 or (key in frameOldValues and frameOldValues[key] == value): + output += self.format_pair(key, value) + else: + output += self.format_pair(key, value, True) + self.changedLines.append(lineNum) + + # Save values as oldValues + newValues = {} + for (key, value) in vals: + newValues[key] = value + self.frameValues[frameId] = newValues + + return output + + def get_highlights(self): + ret = {} + ret[self.changedHighlight] = self.changedLines + return ret + +class LocalsPane(FrameKeyValuePane): + """ Pane that displays local variables """ + def __init__(self, owner, name = 'locals'): + FrameKeyValuePane.__init__(self, owner, name, open_below=True) + + # FIXME: allow users to customize display of args/locals/statics/scope + self.arguments = True + self.show_locals = True + self.show_statics = True + self.show_in_scope_only = True + + def format_variable(self, var): + """ Returns a Tuple of strings "(Type) Name", "Value" for SBValue var """ + val = var.GetValue() + if val is None: + # If the value is too big, SBValue.GetValue() returns None; replace with ... + val = "..." + + return ("(%s) %s" % (var.GetTypeName(), var.GetName()), "%s" % val) + + def get_frame_content(self, frame): + """ Returns list of key-value pairs of local variables in frame """ + vals = frame.GetVariables(self.arguments, + self.show_locals, + self.show_statics, + self.show_in_scope_only) + return [self.format_variable(x) for x in vals] + +class RegistersPane(FrameKeyValuePane): + """ Pane that displays the contents of registers """ + def __init__(self, owner, name = 'registers'): + FrameKeyValuePane.__init__(self, owner, name, open_below=True) + + def format_register(self, reg): + """ Returns a tuple of strings ("name", "value") for SBRegister reg. """ + name = reg.GetName() + val = reg.GetValue() + if val is None: + val = "..." + return (name, val.strip()) + + def get_frame_content(self, frame): + """ Returns a list of key-value pairs ("name", "value") of registers in frame """ + + result = [] + for register_sets in frame.GetRegisters(): + # hack the register group name into the list of registers... + result.append((" = = %s =" % register_sets.GetName(), "")) + + for reg in register_sets: + result.append(self.format_register(reg)) + return result + +class CommandPane(VimPane): + """ Pane that displays the output of an LLDB command """ + def __init__(self, owner, name, open_below, process_required=True): + VimPane.__init__(self, owner, name, open_below) + self.process_required = process_required + + def setCommand(self, command, args = ""): + self.command = command + self.args = args + + def get_content(self, target, controller): + output = "" + if not target: + output = VimPane.MSG_NO_TARGET + elif self.process_required and not target.GetProcess(): + output = VimPane.MSG_NO_PROCESS + else: + (success, output) = controller.getCommandOutput(self.command, self.args) + return output + +class StoppedCommandPane(CommandPane): + """ Pane that displays the output of an LLDB command when the process is + stopped; otherwise displays process status. This class also implements + highlighting for a single line (to show a single-line selected entity.) + """ + def __init__(self, owner, name, open_below): + """ Initialize parent and define highlight to use for selected line. """ + CommandPane.__init__(self, owner, name, open_below) + if have_gui(): + self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_GUI + else: + self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_TERM + self.define_highlight(VimPane.SELECTED_HIGHLIGHT_NAME_TERM, + VimPane.SELECTED_HIGHLIGHT_COLOUR_TERM) + + def get_content(self, target, controller): + """ Returns the output of a command that relies on the process being stopped. + If the process is not in 'stopped' state, the process status is returned. + """ + output = "" + if not target or not target.IsValid(): + output = VimPane.MSG_NO_TARGET + elif not target.GetProcess() or not target.GetProcess().IsValid(): + output = VimPane.MSG_NO_PROCESS + elif target.GetProcess().GetState() == lldb.eStateStopped: + (success, output) = controller.getCommandOutput(self.command, self.args) + else: + (success, output) = controller.getCommandOutput("process", "status") + return output + + def get_highlights(self): + """ Highlight the line under the cursor. Users moving the cursor has + no effect on the selected line. + """ + ret = {} + line = self.get_selected_line() + if line is not None: + ret[self.selectedHighlight] = [line] + return ret + return ret + + def get_selected_line(self): + """ Subclasses implement this to control where the cursor (and selected highlight) + is placed. + """ + return None + +class DisassemblyPane(CommandPane): + """ Pane that displays disassembly around PC """ + def __init__(self, owner, name = 'disassembly'): + CommandPane.__init__(self, owner, name, open_below=True) + + # FIXME: let users customize the number of instructions to disassemble + self.setCommand("disassemble", "-c %d -p" % self.maxHeight) + +class ThreadPane(StoppedCommandPane): + """ Pane that displays threads list """ + def __init__(self, owner, name = 'threads'): + StoppedCommandPane.__init__(self, owner, name, open_below=False) + self.setCommand("thread", "list") + +# FIXME: the function below assumes threads are listed in sequential order, +# which turns out to not be the case. Highlighting of selected thread +# will be disabled until this can be fixed. LLDB prints a '*' anyways +# beside the selected thread, so this is not too big of a problem. +# def get_selected_line(self): +# """ Place the cursor on the line with the selected entity. +# Subclasses should override this to customize selection. +# Formula: selected_line = selected_thread_id + 1 +# """ +# (thread, err) = get_selected_thread(self.target) +# if thread is None: +# return None +# else: +# return thread.GetIndexID() + 1 + +class BacktracePane(StoppedCommandPane): + """ Pane that displays backtrace """ + def __init__(self, owner, name = 'backtrace'): + StoppedCommandPane.__init__(self, owner, name, open_below=False) + self.setCommand("bt", "") + + + def get_selected_line(self): + """ Returns the line number in the buffer with the selected frame. + Formula: selected_line = selected_frame_id + 2 + FIXME: the above formula hack does not work when the function return + value is printed in the bt window; the wrong line is highlighted. + """ + + (frame, err) = get_selected_frame(self.target) + if frame is None: + return None + else: + return frame.GetFrameID() + 2 + +class BreakpointsPane(CommandPane): + def __init__(self, owner, name = 'breakpoints'): + super(BreakpointsPane, self).__init__(owner, name, open_below=False, process_required=False) + self.setCommand("breakpoint", "list") diff --git a/utils/vim-lldb/python-vim-lldb/vim_signs.py b/utils/vim-lldb/python-vim-lldb/vim_signs.py new file mode 100644 index 000000000000..926cc29a7fca --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/vim_signs.py @@ -0,0 +1,73 @@ + +# Classes responsible for drawing signs in the Vim user interface. + +import vim + +class VimSign(object): + SIGN_TEXT_BREAKPOINT_RESOLVED = "B>" + SIGN_TEXT_BREAKPOINT_UNRESOLVED = "b>" + SIGN_TEXT_PC = "->" + SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue' + + # unique sign id (for ':[sign/highlight] define) + sign_id = 1 + + # unique name id (for ':sign place') + name_id = 1 + + # Map of {(sign_text, highlight_colour) --> sign_name} + defined_signs = {} + + def __init__(self, sign_text, buffer, line_number, highlight_colour=None): + """ Define the sign and highlight (if applicable) and show the sign. """ + + # Get the sign name, either by defining it, or looking it up in the map of defined signs + key = (sign_text, highlight_colour) + if not key in VimSign.defined_signs: + name = self.define(sign_text, highlight_colour) + else: + name = VimSign.defined_signs[key] + + self.show(name, buffer.number, line_number) + pass + + def define(self, sign_text, highlight_colour): + """ Defines sign and highlight (if highlight_colour is not None). """ + sign_name = "sign%d" % VimSign.name_id + if highlight_colour is None: + vim.command("sign define %s text=%s" % (sign_name, sign_text)) + else: + self.highlight_name = "highlight%d" % VimSign.name_id + vim.command("highlight %s ctermbg=%s guibg=%s" % (self.highlight_name, + highlight_colour, + highlight_colour)) + vim.command("sign define %s text=%s linehl=%s texthl=%s" % (sign_name, + sign_text, + self.highlight_name, + self.highlight_name)) + VimSign.defined_signs[(sign_text, highlight_colour)] = sign_name + VimSign.name_id += 1 + return sign_name + + + def show(self, name, buffer_number, line_number): + self.id = VimSign.sign_id + VimSign.sign_id += 1 + vim.command("sign place %d name=%s line=%d buffer=%s" % (self.id, name, line_number, buffer_number)) + pass + + def hide(self): + vim.command("sign unplace %d" % self.id) + pass + +class BreakpointSign(VimSign): + def __init__(self, buffer, line_number, is_resolved): + txt = VimSign.SIGN_TEXT_BREAKPOINT_RESOLVED if is_resolved else VimSign.SIGN_TEXT_BREAKPOINT_UNRESOLVED + super(BreakpointSign, self).__init__(txt, buffer, line_number) + +class PCSign(VimSign): + def __init__(self, buffer, line_number, is_selected_thread): + super(PCSign, self).__init__(VimSign.SIGN_TEXT_PC, + buffer, + line_number, + VimSign.SIGN_HIGHLIGHT_COLOUR_PC if is_selected_thread else None) diff --git a/utils/vim-lldb/python-vim-lldb/vim_ui.py b/utils/vim-lldb/python-vim-lldb/vim_ui.py new file mode 100644 index 000000000000..4be346b96f0e --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/vim_ui.py @@ -0,0 +1,235 @@ + +# LLDB UI state in the Vim user interface. + +import os, re, sys +import lldb +import vim +from vim_panes import * +from vim_signs import * + +def is_same_file(a, b): + """ returns true if paths a and b are the same file """ + a = os.path.realpath(a) + b = os.path.realpath(b) + return a in b or b in a + +class UI: + def __init__(self): + """ Declare UI state variables """ + + # Default panes to display + self.defaultPanes = ['breakpoints', 'backtrace', 'locals', 'threads', 'registers', 'disassembly'] + + # map of tuples (filename, line) --> SBBreakpoint + self.markedBreakpoints = {} + + # Currently shown signs + self.breakpointSigns = {} + self.pcSigns = [] + + # Container for panes + self.paneCol = PaneLayout() + + # All possible LLDB panes + self.backtracePane = BacktracePane(self.paneCol) + self.threadPane = ThreadPane(self.paneCol) + self.disassemblyPane = DisassemblyPane(self.paneCol) + self.localsPane = LocalsPane(self.paneCol) + self.registersPane = RegistersPane(self.paneCol) + self.breakPane = BreakpointsPane(self.paneCol) + + def activate(self): + """ Activate UI: display default set of panes """ + self.paneCol.prepare(self.defaultPanes) + + def get_user_buffers(self, filter_name=None): + """ Returns a list of buffers that are not a part of the LLDB UI. That is, they + are not contained in the PaneLayout object self.paneCol. + """ + ret = [] + for w in vim.windows: + b = w.buffer + if not self.paneCol.contains(b.name): + if filter_name is None or filter_name in b.name: + ret.append(b) + return ret + + def update_pc(self, process, buffers, goto_file): + """ Place the PC sign on the PC location of each thread's selected frame """ + + def GetPCSourceLocation(thread): + """ Returns a tuple (thread_index, file, line, column) that represents where + the PC sign should be placed for a thread. + """ + + frame = thread.GetSelectedFrame() + frame_num = frame.GetFrameID() + le = frame.GetLineEntry() + while not le.IsValid() and frame_num < thread.GetNumFrames(): + frame_num += 1 + le = thread.GetFrameAtIndex(frame_num).GetLineEntry() + + if le.IsValid(): + path = os.path.join(le.GetFileSpec().GetDirectory(), le.GetFileSpec().GetFilename()) + return (thread.GetIndexID(), path, le.GetLine(), le.GetColumn()) + return None + + + # Clear all existing PC signs + del_list = [] + for sign in self.pcSigns: + sign.hide() + del_list.append(sign) + for sign in del_list: + self.pcSigns.remove(sign) + del sign + + # Select a user (non-lldb) window + if not self.paneCol.selectWindow(False): + # No user window found; avoid clobbering by splitting + vim.command(":vsp") + + # Show a PC marker for each thread + for thread in process: + loc = GetPCSourceLocation(thread) + if not loc: + # no valid source locations for PCs. hide all existing PC markers + continue + + buf = None + (tid, fname, line, col) = loc + buffers = self.get_user_buffers(fname) + is_selected = thread.GetIndexID() == process.GetSelectedThread().GetIndexID() + if len(buffers) == 1: + buf = buffers[0] + if buf != vim.current.buffer: + # Vim has an open buffer to the required file: select it + vim.command('execute ":%db"' % buf.number) + elif is_selected and vim.current.buffer.name not in fname and os.path.exists(fname) and goto_file: + # FIXME: If current buffer is modified, vim will complain when we try to switch away. + # Find a way to detect if the current buffer is modified, and...warn instead? + vim.command('execute ":e %s"' % fname) + buf = vim.current.buffer + elif len(buffers) > 1 and goto_file: + #FIXME: multiple open buffers match PC location + continue + else: + continue + + self.pcSigns.append(PCSign(buf, line, is_selected)) + + if is_selected and goto_file: + # if the selected file has a PC marker, move the cursor there too + curname = vim.current.buffer.name + if curname is not None and is_same_file(curname, fname): + move_cursor(line, 0) + elif move_cursor: + print "FIXME: not sure where to move cursor because %s != %s " % (vim.current.buffer.name, fname) + + def update_breakpoints(self, target, buffers): + """ Decorates buffer with signs corresponding to breakpoints in target. """ + + def GetBreakpointLocations(bp): + """ Returns a list of tuples (resolved, filename, line) where a breakpoint was resolved. """ + if not bp.IsValid(): + sys.stderr.write("breakpoint is invalid, no locations") + return [] + + ret = [] + numLocs = bp.GetNumLocations() + for i in range(numLocs): + loc = bp.GetLocationAtIndex(i) + desc = get_description(loc, lldb.eDescriptionLevelFull) + match = re.search('at\ ([^:]+):([\d]+)', desc) + try: + lineNum = int(match.group(2).strip()) + ret.append((loc.IsResolved(), match.group(1), lineNum)) + except ValueError as e: + sys.stderr.write("unable to parse breakpoint location line number: '%s'" % match.group(2)) + sys.stderr.write(str(e)) + + return ret + + + if target is None or not target.IsValid(): + return + + needed_bps = {} + for bp_index in range(target.GetNumBreakpoints()): + bp = target.GetBreakpointAtIndex(bp_index) + locations = GetBreakpointLocations(bp) + for (is_resolved, file, line) in GetBreakpointLocations(bp): + for buf in buffers: + if file in buf.name: + needed_bps[(buf, line, is_resolved)] = bp + + # Hide any signs that correspond with disabled breakpoints + del_list = [] + for (b, l, r) in self.breakpointSigns: + if (b, l, r) not in needed_bps: + self.breakpointSigns[(b, l, r)].hide() + del_list.append((b, l, r)) + for d in del_list: + del self.breakpointSigns[d] + + # Show any signs for new breakpoints + for (b, l, r) in needed_bps: + bp = needed_bps[(b, l, r)] + if self.haveBreakpoint(b.name, l): + self.markedBreakpoints[(b.name, l)].append(bp) + else: + self.markedBreakpoints[(b.name, l)] = [bp] + + if (b, l, r) not in self.breakpointSigns: + s = BreakpointSign(b, l, r) + self.breakpointSigns[(b, l, r)] = s + + def update(self, target, status, controller, goto_file=False): + """ Updates debugger info panels and breakpoint/pc marks and prints + status to the vim status line. If goto_file is True, the user's + cursor is moved to the source PC location in the selected frame. + """ + + self.paneCol.update(target, controller) + self.update_breakpoints(target, self.get_user_buffers()) + + if target is not None and target.IsValid(): + process = target.GetProcess() + if process is not None and process.IsValid(): + self.update_pc(process, self.get_user_buffers, goto_file) + + if status is not None and len(status) > 0: + print status + + def haveBreakpoint(self, file, line): + """ Returns True if we have a breakpoint at file:line, False otherwise """ + return (file, line) in self.markedBreakpoints + + def getBreakpoints(self, fname, line): + """ Returns the LLDB SBBreakpoint object at fname:line """ + if self.haveBreakpoint(fname, line): + return self.markedBreakpoints[(fname, line)] + else: + return None + + def deleteBreakpoints(self, name, line): + del self.markedBreakpoints[(name, line)] + + def showWindow(self, name): + """ Shows (un-hides) window pane specified by name """ + if not self.paneCol.havePane(name): + sys.stderr.write("unknown window: %s" % name) + return False + self.paneCol.prepare([name]) + return True + + def hideWindow(self, name): + """ Hides window pane specified by name """ + if not self.paneCol.havePane(name): + sys.stderr.write("unknown window: %s" % name) + return False + self.paneCol.hide([name]) + return True + +global ui +ui = UI() |