# Part of the A-A-P GUI IDE: Tool class for the generic debugger # Copyright (C) 2002-2003 Stichting NLnet Labs # Permission to copy and use this file is specified in the file COPYING. # If this file is missing you can find it here: http://www.a-a-p.org/COPYING # NOTE: This requires wxPython. import os.path import string from wxPython.wx import * from select import select import signal import threading import time #Only needed for debugging. #import sys import Tool import Util import Xterm toollist = [] # Added to in Tool.py gdb_prog_name = None config_prog_key = "DebugTool/program" def gdbProgName(topmodel): """Return the name of the gdb program to use.""" global gdb_prog_name if not gdb_prog_name: # Get the name of the gdb program from the configuration file. import Config default_dict = {config_prog_key : "gdb"} gdb_prog_name = Config.get_conf_key(config_prog_key, topmodel, default_dict) return gdb_prog_name # TODO: do this properly with gettext(). def _(x): return x def canDoActions(item, type): """Return a dictionary that describes how well this Tool can perform actions on ActyItem "item" with type "type".""" if not item.node: return {} return canDoActionsName(item.node.name, type) def canDoActionsName(name, type): """Return a dictionary that describes how well this Tool can perform actions on file "name" with type "type".""" if (os.name in [ 'dos', 'os2', 'nt', 'win32' ] and not os.path.isfile(name) and os.path.isfile(name + ".exe")): name += ".exe" if (not type) and os.path.isfile(name) and os.access(name, os.X_OK): return { "debug": 60, } return {} def getProperties(topmodel): """Properties that this kind of tool supports.""" return { "start_talk" : 1, "set_breakpoint" : 1, "get_breakpoints" : 1, "eval_text" : 1, "debug_command" : 1, } def openItem(item, action, lnum = None, col = None, off = None): """Open ActyItem "item" in this Tool. Creat a new Tool when there isn't one yet, otherwise return the existing one.""" # Always create a new debugger, since it can't debug two things at a time. tool = DebugTool("Debugger", item, action) item.acty.topmodel.toollist.addTool(tool) return tool class DebugTool(Tool.Tool): """A Debug Tool: debug programs with a debugger window.""" def __init__(self, *args, **keyw): apply(Tool.Tool.__init__, (self,) + args, keyw) self.shellwindow = None self.name = "Debug " + self.itemlist[0].name self.breakpoints = [] # List of Breakpoint objects. # XXX HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK XXX # When gvim is already open when we start the main thread hangs and # stops processing events. It's unclear why. # Close gvim to avoid this. # This may be a problem in the GTK version, let's skip this for # MS-Windows. if os.name == 'posix': for tool in self.topmodel.toollist.getList(): try: if tool.gvim_open: tool.close(0) except: pass # XXX HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK XXX def getProperties(self): """Properties that this tool supports.""" return getProperties(self.topmodel) def close(self, shutdown): """Close the tool. Return non-zero if closing is OK.""" # try stopping a running Debugger if self.shellwindow: self.shellwindow.sendCommand("quit\n") # assume it worked... self.topmodel.toollist.delTool(self) self.shellwindow = None return 1 # can shutdown def windowClosed(self): """Debugger window was closed.""" if self.shellwindow: self.topmodel.toollist.delTool(self) self.shellwindow = None def foreground(self, item, node = None, lnum = None): """Move this Tool to the foreground, with the current activity.""" if item: self.setCurrentItem(item) node = item.node if node: if not self.shellwindow: # Open the window that gdb will run in. self.shellwindow = DebugToolWindow(None, self, node) # XXX bring Debugger to the foreground. if self.shellwindow: self.shellwindow.foreground() def getBreakpoints(self, node): """Get breakpoints for "node".""" list = [] for bp in self.breakpoints: if bp.item.node == node: list.append(bp) return list def setBreakpoint(self, what, node, enable, lnum, col, off): if self.shellwindow: self.shellwindow.setBreakpoint(what, node, enable, lnum, col, off) def debugCmd(self, what, node, enable, lnum, col, off): if self.shellwindow: self.shellwindow.debugCmd(what, node, enable, lnum, col, off) def evalText(self, func, text): """Evaluate "text" and return its result.""" if self.shellwindow: self.shellwindow.evalText(func, text) # # The window for the debugger. # # Toolbar images with the command they invoke for gdb. toolbarimages = [ {"name" : "Images/Run.png", "tip" : "Run", "help" : "Run without arguments", "cmd" : "run\n"}, {"name" : "Images/DebugFullSpeed.png", "tip" : "Continue", "help" : "Continue", "cmd" : "cont\n"}, {}, # spacer {"name" : "Images/Step.png", "tip" : "Step into", "help" : "Step into", "cmd" : "step\n"}, {"name" : "Images/Over.png", "tip" : "Step over", "help" : "Step over", "cmd" : "next\n"}, {"name" : "Images/Out.png", "tip" : "Step out", "help" : "Step out", "cmd" : "finish\n"}, ] # Values for gdbstate: What GDB is currently working on. gdb_usercmd = 1 # busy with user command, echo the output gdb_debugcmd = 2 # debugger command, don't echo gdb_prompt = 3 # at the gdb prompt, ready for a command gdb_promptcmd = 4 # at the gdb prompt, user is typing a command # Translate key codes to gdb escape sequences or characters. # TODO: handle more special keys key_dict = {WXK_LEFT : '\033OD', WXK_RIGHT: '\033OC', WXK_UP: '\033OA', WXK_DOWN: '\033OB', 13: '\n' # CR -> LF } pty_available = -1 class DebugToolWindow(wxFrame): def _init_coll_debugmenu_Items(self, parent): id = wxNewId() parent.Append(id, 'Close') EVT_MENU(self, id, self.OnDebugmenuClose) def _init_utils(self): # Add the menus self.menubar = wxMenuBar() self.debugmenu = wxMenu() self._init_coll_debugmenu_Items(self.debugmenu) self.menubar.Append(self.debugmenu, _("&Debug")) self.SetMenuBar(self.menubar) # Add the Toolbar tb = self.CreateToolBar(wxTB_HORIZONTAL | wxNO_BORDER | wxTB_FLAT) tb.SetToolBitmapSize(wxSize(16, 16)) self.toolbarIDs = {} for img in toolbarimages: if img: k = wxNewId() fname = os.path.join(self.tool.topmodel.rootdir, img["name"]) tb.AddSimpleTool(k, wxBitmap(fname, wxBITMAP_TYPE_PNG), img["tip"], img["help"]) self.toolbarIDs[k] = img EVT_TOOL(self, k, self.OnToolClicked) else: tb.AddSeparator() # finalize toolbar creation tb.Realize() def OnToolClicked(self, event): id = event.GetId() self.sendCommand(self.toolbarIDs[id]["cmd"]) def _init_ctrls(self, prnt): from GUItop import toplevel_xpos, toplevel_ypos, \ toplevel_height, border_height, \ debug_width, debug_height wxFrame.__init__(self, id=wxNewId(), name='', parent=prnt, pos=wxPoint(toplevel_xpos, toplevel_ypos + toplevel_height + border_height), size=wxSize(debug_width, debug_height), style=wxDEFAULT_FRAME_STYLE, title='debug tool') self._init_utils() # The main part of the window is the text control. self.textctrl = wxTextCtrl(self, wxNewId(), '', style = wxTE_MULTILINE | wxTE_PROCESS_TAB) EVT_CHAR(self.textctrl, self.OnChar) EVT_RIGHT_DOWN(self.textctrl, self.OnRightDown) EVT_RIGHT_UP(self.textctrl, self.OnRightUp) self.appending = 0 self.cursorpos = 0 # our cursor position (user can change it, but # this is where text will be inserted). self.insertmode = 0 # insert text instead of overwrite def __init__(self, parent, tool, node): self.tool = tool self._init_ctrls(parent) EVT_CLOSE(self, self.OnWindowClose) # Setup for handling the process stuff before starting it. self.process = None self.gdbpid = 0 self.xterm = None self.inputtail = '' self.errortail = '' # GDB state: what we know gdb is busy with. Upon startup it will print # the welcome message, which is like handling a user command (output is # echoed, wait for a prompt). self.gdbstate = gdb_usercmd self.userinput = '' # user input to be send to gdb at the prompt # Debugger commands to be send to gdb. Start with requesting the # current position. "list" is required to make "info line" work. self.gdbcmds = [ ] self.gdbcmds.append(GDBCommand("list\n", 0)) self.gdbcmds.append(GDBCommand("info line\n", 0)) self.listBreakpoints() self.gdbcmds.append(GDBCommand("info source\n", 0, self.sourceHandler, hidePC = 0)) # Handler function for the GDB response. # Will be called: # - handler(0, None) when starting # - handler(1, line) for each received line "line" # - handler(2, None) when the prompt is encountered. self.responseHandler = None self.current_cmd = None # command currently being handled by gdb self.compilationDir = '' # root for file names (hopefully) self.PCdisplayed = 0 # No PC marked yet self.PCfname = None # No PC position known yet # # Create the process that gdb will run in. # from RecPython import program_path cmd = gdbProgName(self.tool.topmodel) cmd = program_path(cmd) if not cmd: # Ask user for the path to gdb. cmd = self.DebugFileDialog("Cannot find gdb. Please enter the full path for the gdb debugger") if not cmd: return import Config Config.set_conf_key(config_prog_key, self.tool.topmodel, cmd) ttyarg = '' if os.name == "posix": # On Unix we can start a separate xterm to run the program in. self.xterm = Xterm.Xterm(self.tool.topmodel) tty = self.xterm.open('Agide debugger') if tty: ttyarg = "-tty=%s" % tty else: self.xterm = None farg = "--fullname" if os.path.basename(node.name) == "none": nodearg = '' else: nodearg = node.fullName() # The result event is used to pass output from gdb to the main thread. EVT_RECINPUT(self, self.OnRecInput) EVT_RECERROR(self, self.OnRecError) # On Unix we use pty's, so that editing works properly. # Need to cache the result, because the second time "pty" is imported # it might actually work! global pty_available if pty_available == -1: try: import pty pty_available = 1 except: pty_available = 0 # XXX error handling if pty_available == 1: pid, fd = pty.fork() if pid == 0: # child if nodearg: os.execlp(cmd, cmd, ttyarg, farg, nodearg) else: os.execlp(cmd, cmd, ttyarg, farg) self.gdbpid = pid self.gdbfd = fd else: EVT_END_PROCESS(self, -1, self.OnProcessEnded) self.process = wxProcess(self) self.process.Redirect() if nodearg: ecmd = '%s %s %s "%s"' % (cmd, ttyarg, farg, nodearg) else: ecmd = '%s %s %s' % (cmd, ttyarg, farg) wxExecute(ecmd, false, self.process) # Start a thread to handle the output from gdb self.read_thread = threading.Thread(target = self.readFromGDB, name = "Read-From-GDB") self.read_thread.setDaemon(true) self.read_thread.start() def DebugFileDialog(self, title): """ Show a file dialog. Used to obtain the name the gdb program. """ dlg = wxFileDialog(self, title, self.dirname, "", "*", wxOPEN) if dlg.ShowModal() == wxID_OK: name = dlg.GetPath() # Remember dir for next time. else: name = None dlg.Destroy() return name def foreground(self): """Bring the window to the foreground.""" self.Show() self.textctrl.SetFocus() self.Raise() def OnDebugmenuClose(self, event): # Let gdb quit, that should close the window (unless it's cancelled). self.sendCommand("quit\n") def OnWindowClose(self, event): """Called when the debug console window is closed.""" if self.xterm: self.xterm.close() self.xterm = None # Let the tool know our window was closed. self.tool.windowClosed() self.Destroy() def OnRightDown(self, event): """Right mouse button down: ignore to avoid it changes the selection.""" pass def OnRightUp(self, event): """Right mouse button up: popup context menu.""" # Create popup menu menu = wxMenu() # popup menu items: copy and paste id = wxNewId() menu.Append(id, _("&Copy"), _("&Copy the selected text")) EVT_MENU(self, id, self.OnPmenuCopy) menu.Enable(id, self.textctrl.CanCopy()) id = wxNewId() menu.Append(id, _("&Paste"), _("&Paste from the clipboard")) EVT_MENU(self, id, self.OnPmenuPaste) menu.Enable(id, self.textctrl.CanPaste()) id = wxNewId() menu.Append(id, _("Paste &Selection"), _("&Paste the selected text")) EVT_MENU(self, id, self.OnPmenuPasteSel) s, e = self.textctrl.GetSelection() menu.Enable(id, s != e) self.PopupMenu(menu, event.GetPosition()) def OnPmenuCopy(self, event): """Popup menu copy handler.""" self.textctrl.Copy() def OnPmenuPaste(self, event): """Popup menu paste handler.""" tdo = wxTextDataObject() wxTheClipboard.Open() ok = wxTheClipboard.GetData(tdo) wxTheClipboard.Close() if ok: text = tdo.GetText() self.userinput = self.userinput + text if self.process: # Echo the text if gdb doesn't use a tty. self.appendText(text) self.handleUserInput() def OnPmenuPasteSel(self, event): """Popup menu paste handler.""" text = self.textctrl.GetStringSelection() self.userinput = self.userinput + text if self.process: # Echo the text if gdb doesn't use a tty. self.appendText(text) self.handleUserInput() def OnChar(self, event): """Text changed in the textctrl: pass it on the the process.""" if self.appending or not (self.process or self.gdbpid): return # Appending text ourselves. c = event.GetKeyCode() # Translate key code to character or escape sequence. str = key_dict.get(c) if not str and c < 256: str = chr(c) if str: self.userinput = self.userinput + str self.handleUserInput() if self.process: # Need to echo the text if stdin doesn't look like a tty to gdb. self.appendText(chr(c)) def appendText(self, text): """Append text in the textctrl at the current cursor position. Only to be used for ordinary text and NL.""" self.checkCursorPosition() text_len = len(text) last_pos = self.textctrl.GetLastPosition() if self.cursorpos < last_pos and not self.insertmode: # Overwrite existing text if self.cursorpos + text_len > last_pos: self.textctrl.Remove(self.cursorpos, last_pos) else: self.textctrl.Remove(self.cursorpos, self.cursorpos + text_len) self.textctrl.WriteText(text) self.cursorpos = self.cursorpos + text_len def checkCursorPosition(self): """Make sure the cursor of the textctrl is where we want it.""" pos = self.textctrl.GetInsertionPoint() if pos != self.cursorpos: self.textctrl.SetInsertionPoint(self.cursorpos) def handleUserInput(self): """Send any pending user input to gdb, but only when gdb is ready for it.""" if self.userinput and (self.gdbstate == gdb_prompt or self.gdbstate == gdb_usercmd or self.gdbstate == gdb_promptcmd): # Avoid any potential race condition by deleting the userinput # before sending it to gdb. inp = self.userinput self.userinput = '' self.gdbWrite(inp) if inp[-1] == '\n': # User typed a command and Enter. # Check if the command requires updating info. line = self.textctrl.GetLineText( self.textctrl.GetNumberOfLines() - 1) idx = string.find(line, "(gdb) ") if idx >= 0: idx = Util.skip_white(line, idx + 5) if (string.find(line, "enable", idx) >= 0 or string.find(line, "disable", idx) >= 0 or string.find(line, "delete", idx) >= 0): self.listBreakpoints() # Don't display the PC, a "run" command may invalidate it. self.displayPC(0) # Need to wait for the prompt now. self.gdbstate = gdb_usercmd else: # User is still typing a command. self.gdbstate = gdb_promptcmd def debugMsg(self, msg): """Print a debugging message. Do this on the original stdout, because our threading doesn't allow writing in the Console.""" t = '' for c in msg: n = ord(c) if n >= 32 and n < 127: t = t + c elif n == 27: t = t + "" elif n == 8: t = t + "" elif n == 13: t = t + "" elif n == 10: t = t + "" else: t = t + ("<0x%x>" % n) sys.__stdout__.write(t + '\n') sys.__stdout__.flush() def gdbWrite(self, text): """Write a string directly to gdb.""" # self.debugMsg("sending to GDB: '%s'" % text) if self.process: self.process.GetOutputStream().write(text) else: os.write(self.gdbfd, text) def readFromGDB(self): """Read text from GDB and display it in our window. Loops until the connection to gdb is lost. >>> Runs in a separate thread <<<. You can't use "print" commands here!""" while 1: # Read what's to be read, append to previously read text. try: proc = self.process except wxPyDeadObjectError: # Happens on Win32: When the window is closed we get a # wxPyDeadObjectError. return if proc is not None: input = self.handleStream(proc.GetInputStream()) error = self.handleStream(proc.GetErrorStream()) # XXX is there a better way to avoid busy waiting? if input == '' and error == '': time.sleep(0.05) elif self.gdbpid > 0: # Hang around in select() until there is something to read or # an error occurred. rfds, wfds, xfds = select([self.gdbfd], [], [self.gdbfd]) if xfds: # Assume gdb has exited. # self.debugMsg("gdb read error, probably exited.") self.OnProcessEnded(None) return if rfds: input = os.read(self.gdbfd, 10000) # self.debugMsg("readFromGDB received '%s'" % input) if not input: # Assume gdb has exited. self.OnProcessEnded(None) return else: # self.debugMsg("readFromGDB nothing received") input = '' error = '' else: # Process must have exited. return # If something was read, send an event to the main thread. if input: wxPostEvent(self, RecInputEvent(input)) if error: wxPostEvent(self, RecErrorEvent(error)) def handleStream(self, stream): """Handle the stdout or stderr of the debugger process.""" # EOF means that there is no data available to be read, # not truly the end of file. if stream.eof(): return '' return stream.read() def OnRecInput(self, event): self.inputtail = self.handleInput(self.inputtail + event.data) def OnRecError(self, event): self.errortail = self.handleInput(self.errortail + event.data) def handleInput(self, text): """Handle text that GDB sent to us. what can be handled, return the remaining.""" endsinprompt = 0 while text: # Find the first NL in the text. If it's found we have a complete # line. "nlidx" is the index of the NL, -1 if not found. # "eolidx" is set to the "nlidx", but if there is a CR just before # it use the index of the CR. # If there is a CR earlier, assume that the text upto this is # overwritten by the text following, remove it. But do remember # the removed text for when echoing a user command. crtext = '' nlidx = string.find(text, '\n') if nlidx <= 0: eolidx = nlidx else: while 1: cridx = string.find(text, '\r', 0, nlidx - 1) if cridx >= 0: crtext = crtext + text[:cridx + 1] text = text[cridx + 1:] nlidx = nlidx - (cridx + 1) else: break if text[nlidx - 1] == '\r': eolidx = nlidx - 1 else: eolidx = nlidx # Don't recognize a prompt when part of a command is echoed. # This sometimes happens when gdb uses the readline library. endsinprompt = (nlidx < 0 and text[-6:] == "(gdb) ") if text[0] == '\032': # The text starts with 0x1a: special message. Can always be # interpreted. Don't echo it to the display. if nlidx < 0: # Incomplete, wait for more return text self.parseGDBline(text[:eolidx]) text = text[nlidx + 1:] else: if nlidx > 0: # Received a full line, check if we recognize something. if string.find(text, "Program exited", 0, nlidx) >= 0: # Program exited, don't display the PC now. self.PCfname = None elif len(text) > 11 and text[0:11] == "Breakpoint ": # User added a breakpoint. self.addedBreak(text[11:eolidx]) if self.gdbstate == gdb_debugcmd: # Parse output from a command we sent to gdb. This is not # echoed to the display. if nlidx < 0: if endsinprompt: break if not self.hitReturnPrompt(text): # Incomplete, wait for more return text # hit-return prompt, pretend it's terminalted with NL. nlidx = len(text) # parse the text depending on the command sent. if self.responseHandler: self.responseHandler(1, text[:eolidx]) text = text[nlidx + 1:] else: # Ordinary text, display up to one line. self.handleInputDisplay(crtext) if nlidx < 0: self.handleInputDisplay(text) break else: self.handleInputDisplay(text[:nlidx + 1]) text = text[nlidx + 1:] self.textctrl.ShowPosition(self.textctrl.GetLastPosition()) if endsinprompt: if self.responseHandler: # Let the handler know the output has finished. self.responseHandler(2, None) self.responseHandler = None self.gdbstate = gdb_prompt self.checkForMore() return '' def hitReturnPrompt(self, text): """Return non-zero if "text" is the hit-return prompt.""" return len(text) > 10 and text[:7] == '---Type' and text[-3:] == '---' def checkForMore(self): """Called whenever something changed the state: check if there are more commands or user input waiting.""" if self.gdbcmds: if self.gdbstate == gdb_prompt or self.gdbstate == gdb_promptcmd: if self.gdbcmds[0].hidePC: self.displayPC(0) # Setup the handler for the gdb response before sending # the command to gdb. # Initialize the handler by calling it once. self.responseHandler = self.gdbcmds[0].handler if self.responseHandler: self.responseHandler(0, None) # Setup echoing or not, also before sending the command to gdb. prevstate = self.gdbstate if self.gdbcmds[0].echo: self.gdbstate = gdb_usercmd else: self.gdbstate = gdb_debugcmd # Delete the command before sending it to gdb to avoid any # potential race conditions. cmd = self.gdbcmds[0].cmd del self.gdbcmds[0] if prevstate == gdb_promptcmd: # Send a CTRL-U to delete the half finished command. # Sorry user! Avoids that typing a few characters blocks # using a toolbar item such as "Next". self.gdbWrite('\025') self.gdbWrite(cmd) else: self.handleUserInput() if self.gdbstate == gdb_prompt: self.displayPC(1) def handleInputDisplay(self, text): """Display "text" in the debugger console, handling special characters. "text" includes CR and/or NL at the end.""" self.appending = 1 # DEBUG # self.debugMsg("GDB: '%s'" % text) # XXX this assumes escape sequences are complete text_len = len(text) sidx = 0 idx = 0 while idx < text_len: c = text[idx] n = ord(c) if n < 32: if idx > sidx: self.appendText(text[sidx:idx]) if c == '\n': self.textctrl.AppendText('\n') self.cursorpos = self.textctrl.GetLastPosition() elif c == '\b': # Backspace: Move one character left self.cursorpos = self.cursorpos - 1 if self.cursorpos < 0: self.cursorpos = 0 elif n == 7: # Ring the bell # XXX should use a visual bell wxBell() elif c == '\r': # CR: cursor to start of line. l = self.textctrl.GetLastPosition() llen = self.textctrl.GetLineLength( self.textctrl.GetNumberOfLines() - 1) self.cursorpos = l - llen elif n == 27: # [K: clear to end of line if (idx + 2 < text_len and text[idx + 1] == '[' and text[idx + 2] == 'K'): self.checkCursorPosition() self.textctrl.Remove(self.cursorpos, self.textctrl.GetLastPosition()) idx = idx + 2 # [%dP: delete N characters if (idx + 3 < text_len and text[idx + 1] == '['): n = idx + 2 while (text[n] in string.digits) and n + 1 < text_len: n = n + 1 if n < text_len and text[n] == 'P': self.checkCursorPosition() self.textctrl.Remove(self.cursorpos, self.cursorpos + string.atoi(text[idx + 2:n])) idx = n # [4h: start insert mode if (idx + 3 < text_len and text[idx + 1] == '[' and text[idx + 2] == '4' and text[idx + 3] == 'h'): self.insertmode = 1 idx = idx + 3 # [4l: end insert mode if (idx + 2 < text_len and text[idx + 1] == '[' and text[idx + 2] == '4' and text[idx + 3] == 'l'): self.insertmode = 0 idx = idx + 3 # TODO: handle other ESC sequences. sidx = idx + 1 idx = idx + 1 if idx > sidx: self.appendText(text[sidx:idx]) # Display cursor in right position (in case BS came last). self.checkCursorPosition() self.appending = 0 def sendCommand(self, cmd): """Send command "cmd" to gdb. This is displayed for the user, don't use this for internal commands!""" self.gdbcmds.append(GDBCommand(cmd, 1)) self.checkForMore() def OnProcessEnded(self, event): if self.process: self.process.Destroy() self.process = None elif self.gdbpid: try: os.kill(self.gdbpid, signal.SIGKILL) except OSError: pass # probably exited already self.gdbpid = 0 self.Close() def parseGDBline(self, line): """Parse a line from GDB. Extract a file position. This is the format: <1a><1a>/path/filename:lnum:off:beg:0x012345. Returns non-zero if the line was recognized.""" line_len = len(line) if line_len < 2 or line[0] != '\032' or line[1] != '\032': return 0 # file name eidx = string.find(line, ":") if eidx < 0: return 0 if eidx == 3 and os.name in [ 'dos', 'os2', 'nt', 'win32' ]: # <1a><1a>c:/file:lnum... eidx = string.find(line, ":", eidx + 1) if eidx < 0: return 0 fname = line[2:eidx] # line number sidx = eidx + 1 eidx = string.find(line, ":", sidx) if eidx < 0: return 0 lnum = int(line[sidx:eidx]) # offset sidx = eidx + 1 eidx = string.find(line, ":", sidx) if eidx < 0: return 0 off = int(line[sidx:eidx]) # Don't know what "beg" stands for. # Don't know what the "0x0234" sidx for. # Check if we are not closing down if self.tool.itemlist: Tool.gotoFile(self.tool.topmodel, self.tool.itemlist[0].acty, fname, lnum = lnum, off = off) # Remove an old PC before storing the new position. self.displayPC(0) self.PCfname = fname self.PClnum = lnum self.PCoff = off return 1 def listBreakpoints(self): """Request the list of breakpoints from GDB.""" # Keep a list of the breakpoints that were found. self.gdbcmds.append(GDBCommand("info break\n", 0, self.breakHandler, hidePC = 0)) def breakHandler(self, when, line): """Parse the response to a "info break" command.""" if when == 0: # startup: clear the list of found breakpoints. self.foundBreakpoints = [] elif when == 2: # Finished listing breakpoints, delete the ones that were not # listed. for bp in self.tool.breakpoints[:]: if not bp.ID in self.foundBreakpoints: self.delBreakpoint(bp) elif line and line[0] in string.digits: # Only handle lines that have this format: # Num Type Disp Enb Address What # 1 breakpoint keep y 0xabcdef in main at main.c:123 try: l = string.split(line, maxsplit = 4) if len(l) == 5 and l[1] == "breakpoint": ID = int(l[0]) enable = (l[3] == 'y') i = string.find(l[4], " at ") e = string.rfind(l[4], ":") if i > 0 and e > i: self.foundBreakpoints.append(ID) fname = l[4][i + 4:e] lnum = int(l[4][e + 1:]) for bp in self.tool.breakpoints: if bp.ID == ID: if bp.enable != enable: bp.enable = enable self.updBreakpoint(bp) return self.addBreakpoint(Tool.Breakpoint(self.tool, ID, enable, self.compilationDir, fname, lnum = lnum)) except StandardError, e: print "Error while parsing break info '%s': %s" % (line, str(e)) def sourceHandler(self, when, line): """Parse the response to a "info source" command. We only need to get the compilation directory, the rest is ingored.""" if (line and len(line) > 25 and line[:25] == "Compilation directory is "): # Make sure the compilation directory ends in a slash. if line[-1] != "/": self.compilationDir = line[25:] + '/' else: self.compilationDir = line[25:] def setBreakpoint(self, what, node, enable, lnum, col, off): """Called by Tool.setBreakpoint() to add/remove/enable/disable a breakpoint.""" do_cont = 0 if self.gdbstate == gdb_usercmd: # Busy with a command. Interrupt it and continue later. self.gdbWrite('\003') do_cont = 1 elif self.gdbstate == gdb_promptcmd: # User is halfway typing a command, remove it with a CTRL-U. self.gdbWrite('\025') if what == "new": # Send the command to GDB, expect the resulting output to create # the breakpoint. clen = len(self.compilationDir) if (len(node.name) > clen and node.name[:clen] == self.compilationDir): name = node.name[clen:] else: name = node.name self.sendCommand("break %s:%d\n" % (name, lnum)) else: for bp in self.tool.breakpoints: if (bp.item.node == node and ((lnum and bp.lnum == lnum) or (off and bp.off == off)) and (what == "del" or bp.enable != enable)): if what == "del": self.sendCommand("delete %d\n" % bp.ID) elif enable: self.sendCommand("enable %d\n" % bp.ID) else: self.sendCommand("disable %d\n" % bp.ID) # GDB doesn't give feedback for these commands, need to # list all breakpoints to find out what actually # changed. self.listBreakpoints() # Do only one breakpoint of a line. break if do_cont: self.sendCommand("cont\n") def debugCmd(self, what, node, enable, lnum, col, off): """Called by Tool.debugCmd() to perform a debugger command.""" if self.gdbstate == gdb_promptcmd: # User is halfway typing a command, remove it with a CTRL-U. self.gdbWrite('\025') # Very simple: the "what" terminology matches what gdb uses. self.sendCommand(what + "\n") def addBreakpoint(self, bp): """Add a new breakpoint to the list of breakpoints.""" if not bp in self.tool.breakpoints: self.tool.breakpoints.append(bp) if bp.item: bp.item.displayBreakpoint("new", bp) def delBreakpoint(self, bp): """Delete a breakpoint from the list of breakpoints.""" if bp in self.tool.breakpoints: self.tool.breakpoints.remove(bp) if bp.item: bp.item.displayBreakpoint("del", bp) def updBreakpoint(self, bp): """Update a breakpoint (e.g., enable/disable).""" if bp.item: bp.item.displayBreakpoint("upd", bp) def addedBreak(self, text): """A breakpoint was added (by the user). "text" is what comes after "Breakpoint ".""" # should get something like: # 0 1 2 3 4 5 6 # 2 at 0x234234: file foo.c, line 1234. l = string.split(text) if len(l) == 7 and l[4][-1] == "," and l[6][-1] == ".": nr = int(l[0]) fname = l[4][:-1] lnum = int(l[6][:-1]) self.addBreakpoint(Tool.Breakpoint(self.tool, nr, 1, self.compilationDir, fname, lnum)) def displayPC(self, show): """When "show" is non-zero: Display any currently known PC. When "show" is zero: Remove any currently marked PC.""" if not self.tool.itemlist: # closing down return if ((show and not self.PCdisplayed and self.PCfname) or (not show and self.PCdisplayed)): Tool.showPCFile(self.tool.topmodel, self.tool.itemlist[0].acty, self.PCfname, lnum = self.PClnum, off = self.PCoff, show = show) self.PCdisplayed = show def evalText(self, func, text): """Evaluate "text" and return its result.""" if self.gdbstate == gdb_prompt: self.evalCallback = func self.evalTextText = text self.evalFirstResult = '' self.gdbcmds.append(GDBCommand("print %s\n" % text, 0, self.evalHandler, hidePC = 0)) self.checkForMore() def evalHandler(self, when, line): """Parse the response to a "print text" command.""" if when == 0: # Init the result to an empty string. self.evalResult = '' elif when == 2: # Finished listing text, invoke the callback. if self.evalResult or self.evalFirstResult: # If it's a single line containing "*)" evaluate as a pointer. # Invoke the callback only after doing that. if (self.evalFirstResult == '' and string.find(self.evalResult, '\n') < 0 and string.find(self.evalResult, '*)') > 0): self.gdbcmds.append(GDBCommand("print *%s\n" % self.evalTextText, 0, self.evalHandler, hidePC = 0)) self.evalFirstResult = self.evalResult + '\n' else: self.evalCallback(self.evalTextText + ' =\n' + self.evalFirstResult + self.evalResult) elif line: if not self.evalResult: # Searching for first line. Only handle lines that have this # format: # $123 = text if line[0] == '$': try: l = string.split(line, maxsplit = 2) if len(l) == 3 and l[1] == "=": self.evalResult = l[2] except StandardError: pass elif line[0] == ' ': # A continuation line. self.evalResult = self.evalResult + '\n' + line elif line[0] == '-': # Prompt for more listing. Reply with 'q' since a long listing # is truncated and may continue quite long. self.gdbWrite('q\n') class GDBCommand: """Class used to store a command that is to be send to GDB.""" def __init__(self, cmd, echo, handler = None, hidePC = 1): self.cmd = cmd # command, including trailing "\n" self.echo = echo # non-zero if command is to be echoed. self.handler = handler # function that handles the response or None self.hidePC = hidePC # hide program counter while executing. # # Event handling for passing text from the thread that waits for output from # gdb (input for us) and passes the text on to the main thread with an event. # EVT_RECINPUT_ID = wxNewEventType() EVT_RECERROR_ID = wxNewEventType() class RecInputEvent(wxPyEvent): """Simple event to pass the received text from gdb to the main thread.""" def __init__(self, data): wxPyEvent.__init__(self) self.SetEventType(EVT_RECINPUT_ID) self.data = data class RecErrorEvent(wxPyEvent): """Simple event to pass the received text from gdb to the main thread.""" def __init__(self, data): wxPyEvent.__init__(self) self.SetEventType(EVT_RECERROR_ID) self.data = data def EVT_RECINPUT(win, func): win.Connect(-1, -1, EVT_RECINPUT_ID, func) def EVT_RECERROR(win, func): win.Connect(-1, -1, EVT_RECERROR_ID, func) # vim: set sw=4 et sts=4 tw=79 fo+=l: