#---------------------------------------------------------------------------- # Name: DebuggerHarness.py # Purpose: # # Author: Matt Fryer # # Created: 7/28/04 # CVS-ID: $Id$ # Copyright: (c) 2005 ActiveGrid, Inc. # License: wxWindows License #---------------------------------------------------------------------------- import bdb import sys import SimpleXMLRPCServer import threading import xmlrpclib import os import types import Queue import traceback import inspect from xml.dom.minidom import getDOMImplementation import atexit import pickle import cStringIO import bz2 if sys.platform.startswith("win"): import win32api _WINDOWS = True else: _WINDOWS = False _VERBOSE = False _DEBUG_DEBUGGER = False class Adb(bdb.Bdb): def __init__(self, harness, queue): bdb.Bdb.__init__(self) self._harness = harness self._userBreak = False self._queue = queue self._knownCantExpandFiles = {} self._knownExpandedFiles = {} def getLongName(self, filename): if not _WINDOWS: return filename if self._knownCantExpandFiles.get(filename): return filename if self._knownExpandedFiles.get(filename): return self._knownExpandedFiles.get(filename) try: newname = win32api.GetLongPathName(filename) self._knownExpandedFiles[filename] = newname return newname except: self._knownCantExpandFiles[filename] = filename return filename def canonic(self, orig_filename): if orig_filename == "<" + orig_filename[1:-1] + ">": return orig_filename filename = self.getLongName(orig_filename) canonic = self.fncache.get(filename) if not canonic: canonic = os.path.abspath(filename) canonic = os.path.normcase(canonic) self.fncache[filename] = canonic return canonic # Overriding this so that we continue to trace even if no breakpoints are set. def set_continue(self): self.stopframe = self.botframe self.returnframe = None self.quitting = 0 def do_clear(self, arg): bdb.Breakpoint.bpbynumber[int(arg)].deleteMe() def user_line(self, frame): if self.in_debugger_code(frame): self.set_step() return message = self.__frame2message(frame) self._harness.interaction(message, frame, "") def user_call(self, frame, argument_list): if self.in_debugger_code(frame): self.set_step() return if self.stop_here(frame): message = self.__frame2message(frame) self._harness.interaction(message, frame, "") def user_return(self, frame, return_value): if self.in_debugger_code(frame): self.set_step() return message = self.__frame2message(frame) self._harness.interaction(message, frame, "") def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): frame.f_locals['__exception__'] = exc_type, exc_value if type(exc_type) == type(''): exc_type_name = exc_type else: exc_type_name = exc_type.__name__ message = "Exception occured: " + repr(exc_type_name) + " See locals.__exception__ for details." traceback.print_exception(exc_type, exc_value, exc_traceback) self._harness.interaction(message, frame, message) def in_debugger_code(self, frame): if _DEBUG_DEBUGGER: return False message = self.__frame2message(frame) return message.count('DebuggerHarness') > 0 def frame2message(self, frame): return self.__frame2message(frame) def __frame2message(self, frame): code = frame.f_code filename = code.co_filename lineno = frame.f_lineno basename = os.path.basename(filename) message = "%s:%s" % (basename, lineno) if code.co_name != "?": message = "%s: %s()" % (message, code.co_name) return message def runFile(self, fileName): self.reset() #global_dict = {} #global_dict['__name__'] = '__main__' try: fileToRun = open(fileName, mode='r') if _VERBOSE: print "Running file ", fileName sys.settrace(self.trace_dispatch) import __main__ exec fileToRun in __main__.__dict__,__main__.__dict__ except SystemExit: pass except: tp, val, tb = sys.exc_info() traceback.print_exception(tp, val, tb) sys.settrace(None) self.quitting = 1 #global_dict.clear() def trace_dispatch(self, frame, event, arg): if self.quitting: return # None # Check for ui events self.readQueue() if event == 'line': return self.dispatch_line(frame) if event == 'call': return self.dispatch_call(frame, arg) if event == 'return': return self.dispatch_return(frame, arg) if event == 'exception': return self.dispatch_exception(frame, arg) print 'Adb.dispatch: unknown debugging event:', `event` return self.trace_dispatch def readQueue(self): while self._queue.qsize(): try: item = self._queue.get_nowait() if item.kill(): self._harness.do_exit(kill=True) elif item.breakHere(): self._userBreak = True elif item.hasBreakpoints(): self.set_all_breakpoints(item.getBreakpoints()) except Queue.Empty: pass def set_all_breakpoints(self, dict): self.clear_all_breaks() for fileName in dict.keys(): lineList = dict[fileName] for lineNumber in lineList: if _VERBOSE: print "Setting break at line ", str(lineNumber), " in file ", self.canonic(fileName) self.set_break(fileName, int(lineNumber)) return "" def stop_here(self, frame): if self._userBreak: return True # (CT) stopframe may now also be None, see dispatch_call. # (CT) the former test for None is therefore removed from here. if frame is self.stopframe: return True while frame is not None and frame is not self.stopframe: if frame is self.botframe: return True frame = frame.f_back return False class BreakNotify(object): def __init__(self, bps=None, break_here=False, kill=False): self._bps = bps self._break_here = break_here self._kill = kill def breakHere(self): return self._break_here def kill(self): return self._kill def getBreakpoints(self): return self._bps def hasBreakpoints(self): return (self._bps != None) class AGXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self, address, logRequests=0): SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests) class BreakListenerThread(threading.Thread): def __init__(self, host, port, queue): threading.Thread.__init__(self) self._host = host self._port = int(port) self._keepGoing = True self._queue = queue self._server = AGXMLRPCServer((self._host, self._port), logRequests=0) self._server.register_function(self.update_breakpoints) self._server.register_function(self.break_requested) self._server.register_function(self.die) def break_requested(self): bn = BreakNotify(break_here=True) self._queue.put(bn) return "" def update_breakpoints(self, pickled_Binary_bpts): dict = pickle.loads(pickled_Binary_bpts.data) bn = BreakNotify(bps=dict) self._queue.put(bn) return "" def die(self): bn = BreakNotify(kill=True) self._queue.put(bn) return "" def run(self): while self._keepGoing: try: self._server.handle_request() except: if _VERBOSE: tp, val, tb = sys.exc_info() print "Exception in BreakListenerThread.run():", str(tp), str(val) self._keepGoing = False def AskToStop(self): self._keepGoing = False if type(self._server) is not types.NoneType: if _VERBOSE: print "Before calling server close on breakpoint server" self._server.server_close() if _VERBOSE: print "Calling server close on breakpoint server" self._server = None class DebuggerHarness(object): def __init__(self): # Host and port for debugger-side RPC server self._hostname = sys.argv[1] self._portNumber = int(sys.argv[2]) # Name the gui proxy object is registered under self._breakPortNumber = int(sys.argv[3]) # Host and port where the gui proxy can be found. self._guiHost = sys.argv[4] self._guiPort = int(sys.argv[5]) # Command to debug. self._command = sys.argv[6] # Strip out the harness' arguments so that the process we run will see argv as if # it was called directly. sys.argv = sys.argv[6:] self._currentFrame = None self._wait = False # Connect to the gui-side RPC server. self._guiServerUrl = 'http://' + self._guiHost + ':' + str(self._guiPort) + '/' if _VERBOSE: print "Connecting to gui server at ", self._guiServerUrl self._guiServer = xmlrpclib.ServerProxy(self._guiServerUrl,allow_none=1) # Start the break listener self._breakQueue = Queue.Queue(50) self._breakListener = BreakListenerThread(self._hostname, self._breakPortNumber, self._breakQueue) self._breakListener.start() # Create the debugger. self._adb = Adb(self, self._breakQueue) # Create the debugger-side RPC Server and register functions for remote calls. self._server = AGXMLRPCServer((self._hostname, self._portNumber), logRequests=0) self._server.register_function(self.set_step) self._server.register_function(self.set_continue) self._server.register_function(self.set_next) self._server.register_function(self.set_return) self._server.register_function(self.set_breakpoint) self._server.register_function(self.clear_breakpoint) self._server.register_function(self.set_all_breakpoints) self._server.register_function(self.attempt_introspection) self._server.register_function(self.execute_in_frame) self._server.register_function(self.add_watch) self._server.register_function(self.request_frame_document) self.frame_stack = [] self.message_frame_dict = {} self.introspection_list = [] atexit.register(self.do_exit) def run(self): self._adb.runFile(self._command) self.do_exit(kill=True) def do_exit(self, kill=False): self._adb.set_quit() self._breakListener.AskToStop() self._server.server_close() try: self._guiServer.quit() except: pass if kill: try: sys.exit() except: pass def set_breakpoint(self, fileName, lineNo): self._adb.set_break(fileName, lineNo) return "" def set_all_breakpoints(self, dict): self._adb.clear_all_breaks() for fileName in dict.keys(): lineList = dict[fileName] for lineNumber in lineList: self._adb.set_break(fileName, int(lineNumber)) if _VERBOSE: print "Setting break at ", str(lineNumber), " in file ", fileName return "" def clear_breakpoint(self, fileName, lineNo): self._adb.clear_break(fileName, lineNo) return "" def add_watch(self, name, text, frame_message, run_once): if len(frame_message) > 0: frame = self.message_frame_dict[frame_message] try: item = eval(text, frame.f_globals, frame.f_locals) return self.get_watch_document(item, name) except: tp, val, tb = sys.exc_info() return self.get_exception_document(tp, val, tb) return "" def execute_in_frame(self, frame_message, command): frame = self.message_frame_dict[frame_message] output = cStringIO.StringIO() out = sys.stdout err = sys.stderr sys.stdout = output sys.stderr = output try: code = compile(command, '', 'single') exec code in frame.f_globals, frame.f_locals return output.getvalue() sys.stdout = out sys.stderr = err except: sys.stdout = out sys.stderr = err tp, val, tb = sys.exc_info() output = cStringIO.StringIO() traceback.print_exception(tp, val, tb, file=output) return output.getvalue() def attempt_introspection(self, frame_message, chain): try: frame = self.message_frame_dict[frame_message] if frame: name = chain.pop(0) if name == 'globals': item = frame.f_globals elif name == 'locals': item = frame.f_locals for name in chain: item = self.getNextItem(item, name) return self.get_introspection_document(item, name) except: tp, val, tb = sys.exc_info() traceback.print_exception(tp, val, tb) return self.get_empty_introspection_document() def getNextItem(self, link, identifier): tp = type(link) if self.isTupleized(identifier): return self.deTupleize(link, identifier) else: if tp == types.DictType or tp == types.DictProxyType: return link[identifier] else: if hasattr(link, identifier): return getattr(link, identifier) if _VERBOSE or True: print "Failed to find link ", identifier, " on thing: ", self.saferepr(link), " of type ", repr(type(link)) return None def isPrimitive(self, item): tp = type(item) return tp is types.IntType or tp is types.LongType or tp is types.FloatType \ or tp is types.BooleanType or tp is types.ComplexType \ or tp is types.StringType def isTupleized(self, value): return value.count('[') def deTupleize(self, link, string1): try: start = string1.find('[') end = string1.find(']') num = int(string1[start+1:end]) return link[num] except: tp,val,tb = sys.exc_info() if _VERBOSE: print "Got exception in deTupleize: ", val return None def wrapAndCompress(self, stringDoc): import bz2 return xmlrpclib.Binary(bz2.compress(stringDoc)) def get_empty_introspection_document(self): doc = getDOMImplementation().createDocument(None, "replacement", None) return self.wrapAndCompress(doc.toxml()) def get_watch_document(self, item, identifier): doc = getDOMImplementation().createDocument(None, "watch", None) top_element = doc.documentElement self.addAny(top_element, identifier, item, doc, 2) return self.wrapAndCompress(doc.toxml()) def get_introspection_document(self, item, identifier): doc = getDOMImplementation().createDocument(None, "replacement", None) top_element = doc.documentElement self.addAny(top_element, identifier, item, doc, 2) return self.wrapAndCompress(doc.toxml()) def get_exception_document(self, name, tp, val, tb): stack = traceback.format_exception(tp, val, tb) wholeStack = "" for line in stack: wholeStack += line doc = getDOMImplementation().createDocument(None, "watch", None) top_element = doc.documentElement item_node = doc.createElement("dict_nv_element") item_node.setAttribute('value', wholeStack) item_node.setAttribute('name', str(name)) top_element.appendChild(item_node) cantIntro = [types.FunctionType, types.LambdaType, types.UnicodeType, types.StringType, types.NoneType, types.IntType, types.LongType, types.FloatType, types.BooleanType] def addAny(self, top_element, name, item, doc, ply): tp = type(item) if tp in DebuggerHarness.cantIntro or ply < 1: self.addNode(top_element,name, item, doc) elif tp is types.TupleType or tp is types.ListType: self.addTupleOrList(top_element, name, item, doc, ply - 1) elif tp is types.DictType or tp is types.DictProxyType: self.addDict(top_element, name, item, doc, ply -1) elif inspect.ismodule(item): self.addModule(top_element, name, item, doc, ply -1) elif inspect.isclass(item) or tp is types.InstanceType: self.addClass(top_element, name, item, doc, ply -1) elif hasattr(item, '__dict__'): self.addDictAttr(top_element, name, item, doc, ply -1) else: self.addNode(top_element,name, item, doc) def canIntrospect(self, item): tp = type(item) if tp in DebuggerHarness.cantIntro: return False elif tp is types.TupleType or tp is types.ListType: return len(item) > 0 elif tp is types.DictType or tp is types.DictProxyType: return len(item) > 0 elif inspect.ismodule(item): return True elif inspect.isclass(item) or tp is types.InstanceType: if hasattr(item, '__dict__'): return True elif hasattr(item, '__name__'): return True elif hasattr(item, '__module__'): return True elif hasattr(item, '__doc__'): return True else: return False elif hasattr(item, '__dict__'): return len(item.__dict__) > 0 else: return False def addNode(self, parent_node, name, item, document): item_node = document.createElement("dict_nv_element") item_node.setAttribute('value', self.saferepr(item)) item_node.setAttribute('name', str(name)) introVal = str(self.canIntrospect(item)) item_node.setAttribute('intro', str(introVal)) parent_node.appendChild(item_node) def addTupleOrList(self, top_node, name, tupple, doc, ply): tupleNode = doc.createElement('tuple') tupleNode.setAttribute('name', str(name)) tupleNode.setAttribute('value', self.saferepr(tupple)) top_node.appendChild(tupleNode) count = 0 for item in tupple: self.addAny(tupleNode, name +'[' + str(count) + ']',item, doc, ply -1) count += 1 def addDictAttr(self, root_node, name, thing, document, ply): dict_node = document.createElement('thing') dict_node.setAttribute('name', name) dict_node.setAttribute('value', self.saferepr(thing)) root_node.appendChild(dict_node) self.addDict(dict_node, '', thing.__dict__, document, ply) # Not decreminting ply def addDict(self, root_node, name, dict, document, ply): if name != '': dict_node = document.createElement('dict') dict_node.setAttribute('name', name) dict_node.setAttribute('value', self.saferepr(dict)) root_node.appendChild(dict_node) else: dict_node = root_node for key in dict.keys(): strkey = str(key) try: value = dict[key] self.addAny(dict_node, strkey, value, document, ply-1) except: if _VERBOSE: tp,val,tb=sys.exc_info() print "Error recovering key: ", str(key), " from node ", str(name), " Val = ", str(val) traceback.print_exception(tp, val, tb) def addClass(self, root_node, name, class_item, document, ply): item_node = document.createElement('class') item_node.setAttribute('name', str(name)) item_node.setAttribute('value', self.saferepr(class_item)) root_node.appendChild(item_node) try: if hasattr(class_item, '__dict__'): self.addDict(item_node, '', class_item.__dict__, document, ply -1) except: tp,val,tb=sys.exc_info() if _VERBOSE: traceback.print_exception(tp, val, tb) try: if hasattr(class_item, '__name__'): self.addAny(item_node,'__name__',class_item.__name__, document, ply -1) except: tp,val,tb=sys.exc_info() if _VERBOSE: traceback.print_exception(tp, val, tb) try: if hasattr(class_item, '__module__'): self.addAny(item_node, '__module__', class_item.__module__, document, ply -1) except: tp,val,tb=sys.exc_info() if _VERBOSE: traceback.print_exception(tp, val, tb) try: if hasattr(class_item, '__doc__'): self.addAny(item_node, '__doc__', class_item.__doc__, document, ply -1) except: tp,val,tb=sys.exc_info() if _VERBOSE: traceback.print_exception(tp, val, tb) try: if hasattr(class_item, '__bases__'): self.addAny(item_node, '__bases__', class_item.__bases__, document, ply -1) except: tp,val,tb=sys.exc_info() if _VERBOSE: traceback.print_exception(tp, val, tb) def addModule(self, root_node, name, module_item, document, ply): item_node = document.createElement('module') item_node.setAttribute('name', str(name)) item_node.setAttribute('value', self.saferepr(module_item)) root_node.appendChild(item_node) try: if hasattr(module_item, '__file__'): self.addAny(item_node, '__file__', module_item.__file__, document, ply -1) except: pass try: if hasattr(module_item, '__doc__'): self.addAny(item_node,'__doc__', module_item.__doc__, document, ply -1) except: pass def getFrameXML(self, base_frame): self.frame_stack = [] frame = base_frame while frame is not None: if((frame.f_code.co_filename.count('DebuggerHarness.py') == 0) or _DEBUG_DEBUGGER): self.frame_stack.append(frame) frame = frame.f_back self.frame_stack.reverse() self.message_frame_dict = {} doc = getDOMImplementation().createDocument(None, "stack", None) top_element = doc.documentElement numberFrames = len(self.frame_stack) for index in range(numberFrames): frame = self.frame_stack[index] message = self._adb.frame2message(frame) # We include globals and locals only for the last frame as an optimization for cases # where there are a lot of frames. self.addFrame(frame, top_element, doc, includeContent=(index == numberFrames - 1)) return doc.toxml() def addFrame(self, frame, root_element, document, includeContent=False): frameNode = document.createElement('frame') root_element.appendChild(frameNode) code = frame.f_code filename = code.co_filename frameNode.setAttribute('file', str(filename)) frameNode.setAttribute('line', str(frame.f_lineno)) message = self._adb.frame2message(frame) frameNode.setAttribute('message', message) self.message_frame_dict[message] = frame if includeContent: self.addDict(frameNode, "locals", frame.f_locals, document, 2) self.addNode(frameNode, "globals", frame.f_globals, document) def request_frame_document(self, message): frame = self.message_frame_dict[message] doc = getDOMImplementation().createDocument(None, "stack", None) top_element = doc.documentElement if frame: self.addFrame(frame, top_element, doc, includeContent=True) return xmlrpclib.Binary(bz2.compress(doc.toxml())) def getRepr(self, varName, globals, locals): try: return repr(eval(varName, globals, locals)) except: return 'Error: Could not recover value.' def saferepr(self, thing): try: try: return repr(thing) except: return str(type(thing)) except: tp, val, tb = sys.exc_info() #traceback.print_exception(tp, val, tb) return repr(val) # The debugger calls this method when it reaches a breakpoint. def interaction(self, message, frame, info): if _VERBOSE: print 'hit debug side interaction' self._adb._userBreak = False self._currentFrame = frame done = False while not done: try: xml = self.getFrameXML(frame) arg = xmlrpclib.Binary(bz2.compress(xml)) if _VERBOSE: print '============== calling gui side interaction============' self._guiServer.interaction(xmlrpclib.Binary(message), arg, info) if _VERBOSE: print 'after interaction' done = True except: tp, val, tb = sys.exc_info() if True or _VERBOSE: print 'Error contacting GUI server!: ' try: traceback.print_exception(tp, val, tb) except: print "Exception printing traceback", tp, val, tb = sys.exc_info() traceback.print_exception(tp, val, tb) done = False # Block while waiting to be called back from the GUI. Eventually, self._wait will # be set false by a function on this side. Seems pretty lame--I'm surprised it works. self.waitForRPC() def waitForRPC(self): self._wait = True while self._wait : try: if _VERBOSE: print "+++ in harness wait for rpc, before handle_request" self._server.handle_request() if _VERBOSE: print "+++ in harness wait for rpc, after handle_request" except: if _VERBOSE: tp, val, tb = sys.exc_info() print "Got waitForRpc exception : ", repr(tp), ": ", val #time.sleep(0.1) def set_step(self): self._adb.set_step() self._wait = False return "" def set_continue(self): self._adb.set_continue() self._wait = False return "" def set_next(self): self._adb.set_next(self._currentFrame) self._wait = False return "" def set_return(self): self._adb.set_return(self._currentFrame) self._wait = False return "" if __name__ == '__main__': try: harness = DebuggerHarness() harness.run() harness.do_exit(kill=True) except SystemExit: print "Exiting..." except: tp, val, tb = sys.exc_info() traceback.print_exception(tp, val, tb)