diff options
author | Daniel Malea <daniel.malea@gmail.com> | 2013-10-09 22:11:30 +0000 |
---|---|---|
committer | Daniel Malea <daniel.malea@gmail.com> | 2013-10-09 22:11:30 +0000 |
commit | 4c3261d1b3bfa47870ad0eb23e07306754c4c0f7 (patch) | |
tree | e240e6375c69a56acbe1cf89516e91ef3d52f91e | |
parent | 0354b92992a537330e67d280cf65a232a8ea1119 (diff) | |
download | bcm5719-llvm-4c3261d1b3bfa47870ad0eb23e07306754c4c0f7.tar.gz bcm5719-llvm-4c3261d1b3bfa47870ad0eb23e07306754c4c0f7.zip |
Initial checkin of curses-based LLDB UI (lui)
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
llvm-svn: 192326
-rw-r--r-- | lldb/utils/lui/Readme | 36 | ||||
-rw-r--r-- | lldb/utils/lui/breakwin.py | 28 | ||||
-rw-r--r-- | lldb/utils/lui/commandwin.py | 93 | ||||
-rwxr-xr-x | lldb/utils/lui/cui.py | 225 | ||||
-rw-r--r-- | lldb/utils/lui/debuggerdriver.py | 129 | ||||
-rw-r--r-- | lldb/utils/lui/eventwin.py | 16 | ||||
-rw-r--r-- | lldb/utils/lui/lldbutil.py | 890 | ||||
-rwxr-xr-x | lldb/utils/lui/lui.py | 125 | ||||
-rwxr-xr-x | lldb/utils/lui/sandbox.py | 58 | ||||
-rw-r--r-- | lldb/utils/lui/sourcewin.py | 239 | ||||
-rw-r--r-- | lldb/utils/lui/statuswin.py | 28 |
11 files changed, 1867 insertions, 0 deletions
diff --git a/lldb/utils/lui/Readme b/lldb/utils/lui/Readme new file mode 100644 index 00000000000..7ba51ce8110 --- /dev/null +++ b/lldb/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/lldb/utils/lui/breakwin.py b/lldb/utils/lui/breakwin.py new file mode 100644 index 00000000000..1538f1ef223 --- /dev/null +++ b/lldb/utils/lui/breakwin.py @@ -0,0 +1,28 @@ +import cui +import curses +import lldb, lldbutil + +class BreakWin(cui.TitledWin): + def __init__(self, driver, x, y, w, h): + super(BreakWin, self).__init__(x, y, w, h, 'Breakpoints') + self.win.scrollok(1) + super(BreakWin, self).draw() + self.driver = driver + + def handleEvent(self, event): + if isinstance(event, lldb.SBEvent): + if lldb.SBBreakpoint.EventIsBreakpointEvent(event): + self.update() + + def update(self): + target = self.driver.getTarget() + if not target.IsValid(): + return + self.win.addstr(0, 0, '') + for i in range(0, target.GetNumBreakpoints()): + bp = target.GetBreakpointAtIndex(i) + if bp.IsInternal(): + continue + text = lldbutil.get_description(bp) + self.win.addstr(text) + self.win.addstr('\n') diff --git a/lldb/utils/lui/commandwin.py b/lldb/utils/lui/commandwin.py new file mode 100644 index 00000000000..2481c8c61fa --- /dev/null +++ b/lldb/utils/lui/commandwin.py @@ -0,0 +1,93 @@ +import cui +import curses +import lldb + +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 = "" + + driver.setSize(w, h) + + self.win.scrollok(1) + + self.driver = driver + self.history = History() + + def enterCallback(content): + self.handleCommand(content) + def tabCompleteCallback(content): + pass # TODO: implement + + 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) + diff --git a/lldb/utils/lui/cui.py b/lldb/utils/lui/cui.py new file mode 100755 index 00000000000..0c1cf1ccf4d --- /dev/null +++ b/lldb/utils/lui/cui.py @@ -0,0 +1,225 @@ +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 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_GREEN, 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/lldb/utils/lui/debuggerdriver.py b/lldb/utils/lui/debuggerdriver.py new file mode 100644 index 00000000000..7836f19ed85 --- /dev/null +++ b/lldb/utils/lui/debuggerdriver.py @@ -0,0 +1,129 @@ + +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/lldb/utils/lui/eventwin.py b/lldb/utils/lui/eventwin.py new file mode 100644 index 00000000000..2814b800bed --- /dev/null +++ b/lldb/utils/lui/eventwin.py @@ -0,0 +1,16 @@ +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/lldb/utils/lui/lldbutil.py b/lldb/utils/lui/lldbutil.py new file mode 100644 index 00000000000..a7fcee8a16a --- /dev/null +++ b/lldb/utils/lui/lldbutil.py @@ -0,0 +1,890 @@ +""" +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/lldb/utils/lui/lui.py b/lldb/utils/lui/lui.py new file mode 100755 index 00000000000..c4a5254d19d --- /dev/null +++ b/lldb/utils/lui/lui.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +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/lldb/utils/lui/sandbox.py b/lldb/utils/lui/sandbox.py new file mode 100755 index 00000000000..0ac3093768e --- /dev/null +++ b/lldb/utils/lui/sandbox.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +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( 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")) + self.wins.append(cui.TitledWin(w2, h2, w2, h2, "Test Window 4")) + + 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) + +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/lldb/utils/lui/sourcewin.py b/lldb/utils/lui/sourcewin.py new file mode 100644 index 00000000000..9fd15db4761 --- /dev/null +++ b/lldb/utils/lui/sourcewin.py @@ -0,0 +1,239 @@ +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, total_lines): + half = self.height / 2 + if total_lines == 0: + self.viewline = 0 + else: + lines_before_pc = self.pc_line + lines_after_pc = total_lines - self.pc_line + if lines_before_pc < half: + self.viewline = 0 + elif lines_after_pc < half: + self.viewline = total_lines - self.height + 1 + 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)) + if self.viewline + self.height > len(self.content) + 1: + raise Exception("viewline too large: PC=%d viewline=%d (to %d of %d)" % (self.pc_line, + self.viewline, + self.viewline + self.height - 1, + total_lines)) + + 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(len(self.content)) + + 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() + for i in range(self.viewline, self.viewline + self.height - 1): + if i > len(content) - 1: + raise Exception("Out of range content (%d-%d of %d)" % (self.viewline, + self.viewline + self.height - 1, + len(content))) + + 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/lldb/utils/lui/statuswin.py b/lldb/utils/lui/statuswin.py new file mode 100644 index 00000000000..95a74d763ed --- /dev/null +++ b/lldb/utils/lui/statuswin.py @@ -0,0 +1,28 @@ +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)] + text = '' + for key in self.keys: + text = text + '{0} {1} '.format(key[0], key[1]) + self.setText(text) + + 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 + |