class WdbRequest(object, Bdb): """Wdb debugger main class""" __metaclass__ = MetaWdbRequest def __init__(self, ports, skip=None): MetaWdbRequest._last_inst = self self.obj_cache = {} try: Bdb.__init__(self, skip=skip) except TypeError: Bdb.__init__(self) self.begun = False self.connected = False self.make_web_socket(ports) breaks_per_file_lno = Breakpoint.bplist.values() for bps in breaks_per_file_lno: breaks = list(bps) for bp in breaks: args = bp.file, bp.line, bp.temporary, bp.cond self.set_break(*args) log.info("Resetting break %s" % repr(args)) def better_repr(self, obj): try: if isinstance(obj, basestring): raise TypeError() iter(obj) except TypeError: self.obj_cache[id(obj)] = obj return '<a href="%d" class="inspect">%s</a>' % (id(obj), escape(repr(obj))) if isinstance(obj, dict): if type(obj) != dict: dict_repr = type(obj).__name__ + "({" closer = "})" else: dict_repr = "{" closer = "}" dict_repr += ", ".join([repr(key) + ": " + self.better_repr(val) for key, val in obj.items()]) dict_repr += closer return dict_repr if type(obj) == list: iter_repr = "[" closer = "]" elif type(obj) == set: iter_repr = "{" closer = "}" elif type(obj) == tuple: iter_repr = "(" closer = ")" else: iter_repr = escape(obj.__class__.__name__) + "([" closer = "])" iter_repr += ", ".join([self.better_repr(val) for val in obj]) iter_repr += closer return iter_repr @contextmanager def capture_output(self, with_hook=True): self.hooked = "" def display_hook(obj): # That's some dirty hack self.hooked += self.better_repr(obj) stdout, stderr = sys.stdout, sys.stderr if with_hook: d_hook = sys.displayhook sys.displayhook = display_hook sys.stdout, sys.stderr = StringIO(), StringIO() out, err = [], [] try: yield out, err finally: out.extend(sys.stdout.getvalue().splitlines()) err.extend(sys.stderr.getvalue().splitlines()) if with_hook: sys.displayhook = d_hook sys.stdout, sys.stderr = stdout, stderr def dmp(self, thing): return dict( (escape(key), {"val": self.better_repr(getattr(thing, key)), "type": type(getattr(thing, key)).__name__}) for key in dir(thing) ) def make_web_socket(self, ports): log.info("Creating WebSocket") for port in ports: self.ws = WebSocket("0.0.0.0", port) if self.ws.status == "OK": return time.sleep(0.100) raise WsError("No port could be opened") def wsgi_trace(self, app, environ, start_response): def wsgi_with_trace(environ, start_response): self.quitting = 0 self.begun = False self.reset() frame = sys._getframe() while frame: frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back self.stopframe = sys._getframe().f_back self.stoplineno = -1 sys.settrace(self.trace_dispatch) try: appiter = app(environ, start_response) except BdbQuit: sys.settrace(None) start_response("200 OK", [("Content-Type", "text/html")]) yield "<h1>BdbQuit</h1><p>Wdb was interrupted</p>" else: for item in appiter: yield item hasattr(appiter, "close") and appiter.close() sys.settrace(None) self.ws.force_close() return wsgi_with_trace(environ, start_response) def get_file(self, filename, html_escape=True): checkcache(filename) file_ = "".join(getlines(filename)) if not html_escape: return file_ return escape(file_) def handle_connection(self): if self.connected: try: self.send("Ping") except: log.exception("Ping Failed") self.connected = False if not self.connected: self.ws.wait_for_connect() self.connected = True def get_trace(self, frame, tb, w_code=None): frames = [] stack, current = self.get_stack(frame, tb) for i, (frame, lno) in enumerate(stack): code = frame.f_code filename = code.co_filename if filename == "<wdb>" and w_code: line = w_code else: checkcache(filename) line = getline(filename, lno, frame.f_globals) line = line and line.strip() startlnos = dis.findlinestarts(code) lastlineno = list(startlnos)[-1][1] frames.append( { "file": filename, "function": code.co_name, "flno": code.co_firstlineno, "llno": lastlineno, "lno": lno, "code": escape(line), "level": i, } ) return stack, frames, current def handle_exc(self): type_, value = exc_info()[:2] return '<a title="%s">%s: %s</a>' % ( escape(traceback.format_exc().replace('"', "'")), escape(type_.__name__), escape(str(value)), ) def interaction(self, frame, tb=None, exception="Wdb", exception_description="Set Trace"): if not self.ws: raise BdbQuit() try: self._interaction(frame, tb, exception, exception_description) except WsError: log.exception("Websocket Error during interaction. Starting again") self.handle_connection() self.interaction(frame, tb, exception, exception_description) def send(self, data): self.ws.send(data) def receive(self): message = None while not message: rv = self.ws.receive() if rv == "CLOSED": raise WsError message = rv return message def _interaction(self, frame, tb, exception, exception_description): log.debug("Interaction for %r %r %r %r" % (frame, tb, exception, exception_description)) stack, trace, current_index = self.get_trace(frame, tb) current = trace[current_index] locals_ = map(lambda x: x[0].f_locals, stack) if self.begun: self.send("Trace|%s" % dump({"trace": trace, "cwd": os.getcwd()})) current_file = current["file"] self.send( "Check|%s" % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()}) ) else: self.begun = True while True: try: message = self.receive() if "|" in message: pipe = message.index("|") cmd = message[:pipe] data = message[pipe + 1 :] else: cmd = message data = "" def fail(title=None, message=None): if message is None: message = self.handle_exc() else: message = escape(message) self.send("Echo|%s" % dump({"for": escape(title or "%s failed" % cmd), "val": message})) log.debug("Cmd %s #Data %d" % (cmd, len(data))) if cmd == "Start": self.send("Init|%s" % dump({"cwd": os.getcwd()})) self.send("Title|%s" % dump({"title": exception, "subtitle": exception_description})) self.send("Trace|%s" % dump({"trace": trace})) current_file = current["file"] self.send( "Check|%s" % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()}) ) elif cmd == "Select": current_index = int(data) current = trace[current_index] current_file = current["file"] self.send( "Check|%s" % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()}) ) elif cmd == "File": current_file = current["file"] self.send( "Select|%s" % dump( { "frame": current, "breaks": self.get_file_breaks(current_file), "file": self.get_file(current_file), "name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest(), } ) ) elif cmd == "NoFile": self.send("Select|%s" % dump({"frame": current, "breaks": self.get_file_breaks(current["file"])})) elif cmd == "Inspect": try: thing = self.obj_cache.get(int(data)) except Exception: fail() continue self.send("Dump|%s" % dump({"for": escape(repr(thing)), "val": self.dmp(thing)})) elif cmd == "Dump": globals_ = dict(stack[current_index][0].f_globals) try: thing = eval(data, globals_, locals_[current_index]) except Exception: fail() continue else: self.send( "Dump|%s" % dump({"for": escape(u"%s ⟶ %s " % (data, repr(thing))), "val": self.dmp(thing)}) ) elif cmd == "Trace": self.send("Trace|%s" % dump({"trace": trace})) elif cmd == "Eval": redir = None raw_data = data = data.strip() if "!>" in data: data, redir = data.split("!>") data = data.strip() redir = redir.strip() elif data.startswith("!<"): filename = data[2:].strip() try: with open(filename, "r") as f: data = f.read() except Exception: fail("Unable to read from file %s" % filename) continue globals_ = dict(stack[current_index][0].f_globals) # Hack for function scope eval globals_.update(locals_[current_index]) globals_.setdefault("_pprint", pprint) with self.capture_output(with_hook=redir is None) as (out, err): try: compiled_code = compile(data, "<stdin>", "single") l = locals_[current_index] exec compiled_code in globals_, l except Exception: self.hooked = self.handle_exc() if redir: try: with open(redir, "w") as f: f.write("\n".join(out) + "\n".join(err) + "\n") except Exception: fail("Unable to write to file %s" % redir) continue self.send( "Print|%s" % dump({"for": escape(raw_data), "result": escape("Written to file %s" % redir)}) ) else: self.send( "Print|%s" % dump( { "for": escape(raw_data), "result": self.hooked + escape("\n".join(out) + "\n".join(err)), } ) ) elif cmd == "Ping": self.send("Pong") elif cmd == "Step": if hasattr(self, "botframe"): self.set_step() break elif cmd == "Next": if hasattr(self, "botframe"): self.set_next(stack[current_index][0]) break elif cmd == "Continue": if hasattr(self, "botframe"): self.set_continue() break elif cmd == "Return": if hasattr(self, "botframe"): self.set_return(stack[current_index][0]) break elif cmd == "Until": if hasattr(self, "botframe"): self.set_until(stack[current_index][0]) break elif cmd in ("TBreak", "Break"): break_fail = lambda x: fail("Break on %s failed" % data, message=x) if ":" in data: fn, lno = data.split(":") else: fn, lno = current["file"], data cond = None if "," in lno: lno, cond = lno.split(",") cond = cond.lstrip() try: lno = int(lno) except: break_fail("Wrong breakpoint format must be " "[file:]lno[,cond].") continue line = getline(fn, lno, stack[current_index][0].f_globals) if not line: break_fail("Line does not exist") continue line = line.strip() if not line or (line[0] == "#") or (line[:3] == '"""') or line[:3] == "'''": break_fail("Blank line or comment") continue first_rv = rv = self.set_break(fn, lno, int(cmd == "TBreak"), cond) if rv is not None: for path in sys.path: rv = self.set_break(os.path.join(path, fn), lno, int(cmd == "TBreak"), cond) if rv is None: break if rv is None: log.info("Break set at %s:%d [%s]" % (fn, lno, rv)) if fn == current["file"]: self.send("BreakSet|%s" % dump({"lno": lno, "cond": cond})) else: self.send("BreakSet|%s" % dump({})) else: break_fail(first_rv) elif cmd == "Unbreak": lno = int(data) current_file = current["file"] log.info("Break unset at %s:%d" % (current_file, lno)) self.clear_break(current_file, lno) self.send("BreakUnset|%s" % dump({"lno": lno})) elif cmd == "Jump": lno = int(data) if current_index != len(trace) - 1: log.error("Must be at bottom frame") continue try: stack[current_index][0].f_lineno = lno except ValueError: fail() continue trace[current_index]["lno"] = lno self.send("Trace|%s" % dump({"trace": trace})) self.send("Select|%s" % dump({"frame": current, "breaks": self.get_file_breaks(current["file"])})) elif cmd == "Complete": current_file = current["file"] file_ = self.get_file(current_file, False).decode("utf-8") lines = file_.split(u"\n") lno = trace[current_index]["lno"] line_before = lines[lno - 1] indent = len(line_before) - len(line_before.lstrip()) segments = data.split(u"\n") for segment in reversed(segments): line = u" " * indent + segment lines.insert(lno - 1, line) script = Script(u"\n".join(lines), lno - 1 + len(segments), len(segments[-1]) + indent, "") try: completions = script.complete() except: log.exception("Completion failed") self.send( "Log|%s" % dump({"message": "Completion failed for %s" % "\n".join(reversed(segments))}) ) else: fun = script.get_in_function_call() self.send( "Suggest|%s" % dump( { "params": { "params": [p.get_code().replace("\n", "") for p in fun.params], "index": fun.index, "module": fun.module.path, "call_name": fun.call_name, } if fun else None, "completions": [ { "base": comp.word[: len(comp.word) - len(comp.complete)], "complete": comp.complete, "description": comp.description, } for comp in completions if comp.word.endswith(comp.complete) ], } ) ) elif cmd == "Quit": if hasattr(self, "botframe"): self.set_continue() raise BdbQuit() break else: log.warn("Unknown command %s" % cmd) except BdbQuit: raise except Exception: try: exc = self.handle_exc() type_, value = exc_info()[:2] link = ( '<a href="https://github.com/Kozea/wdb/issues/new?' 'title=%s&body=%s&labels=defect" class="nogood">' "Please click here to report it on Github</a>" ) % ( quote("%s: %s" % (type_.__name__, str(value))), quote("```\n%s\n```\n" % traceback.format_exc()), ) self.send( "Echo|%s" % dump({"for": escape("Error in Wdb, this is bad"), "val": exc + "<br>" + link}) ) except: self.send( "Echo|%s" % dump( { "for": escape("Too many errors"), "val": escape("Don't really know what to say. " "Maybe it will work tomorrow."), } ) ) continue def user_call(self, frame, argument_list): """This method is called when there is the remote possibility that we ever need to stop in this function.""" if self.stop_here(frame): fun = frame.f_code.co_name log.info("Calling: %r" % fun) self.handle_connection() self.send("Echo|%s" % dump({"for": "__call__", "val": fun})) self.interaction(frame) def user_line(self, frame): """This function is called when we stop or break at this line.""" log.info("Stopping at line %r:%d" % (frame.f_code.co_filename, frame.f_lineno)) self.handle_connection() log.debug("User Line Interaction for %r" % frame) self.interaction(frame) def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" frame.f_locals["__return__"] = return_value log.info("Returning from %r with value: %r" % (frame.f_code.co_name, return_value)) self.handle_connection() self.send("Echo|%s" % dump({"for": "__return__", "val": return_value})) self.interaction(frame) def user_exception(self, frame, exc_info): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" log.error("Exception", exc_info=exc_info) type_, value, tb = exc_info frame.f_locals["__exception__"] = type_, value exception = type_.__name__ exception_description = str(value) self.handle_connection() self.send( "Echo|%s" % dump({"for": "__exception__", "val": escape("%s: %s") % (exception, exception_description)}) ) if not self.begun: frame = None self.interaction(frame, tb, exception, exception_description) def do_clear(self, arg): log.info("Closing %r" % arg) self.clear_bpbynumber(arg) def dispatch_exception(self, frame, arg): self.user_exception(frame, arg) if self.quitting: raise BdbQuit return self.trace_dispatch def recursive(self, g, l): # Inspect curent debugger vars through pdb sys.settrace(None) from pdb import Pdb p = Pdb() sys.call_tracing(p.run, ("1/0", g, l)) sys.settrace(self.trace_dispatch) self.lastcmd = p.lastcmd
class WdbRequest(object, Bdb): """Wdb debugger main class""" __metaclass__ = MetaWdbRequest def __init__(self, port, skip=None): MetaWdbRequest._last_inst = self try: Bdb.__init__(self, skip=skip) except TypeError: Bdb.__init__(self) self.begun = False self.connected = False self.make_web_socket(port) breaks_per_file_lno = Breakpoint.bplist.values() for bps in breaks_per_file_lno: breaks = list(bps) for bp in breaks: args = bp.file, bp.line, bp.temporary, bp.cond self.set_break(*args) log.info('Resetting break %s' % repr(args)) def make_web_socket(self, port): log.info('Creating WebSocket') self.ws = WebSocket('0.0.0.0', port) def wsgi_trace(self, app, environ, start_response): def wsgi_with_trace(environ, start_response): self.quitting = 0 self.begun = False self.reset() frame = sys._getframe() while frame: frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back self.stopframe = sys._getframe().f_back self.stoplineno = -1 sys.settrace(self.trace_dispatch) try: appiter = app(environ, start_response) except BdbQuit: sys.settrace(None) start_response('200 OK', [('Content-Type', 'text/html')]) yield '<h1>BdbQuit</h1><p>Wdb was interrupted</p>' else: for item in appiter: yield item hasattr(appiter, 'close') and appiter.close() sys.settrace(None) self.ws.force_close() MetaWdbRequest._last_inst = None return wsgi_with_trace(environ, start_response) def get_file(self, filename, html_escape=True): checkcache(filename) file_ = ''.join(getlines(filename)) if not html_escape: return file_ return escape(file_) def handle_connection(self): if self.connected: try: self.send('Ping') except: log.exception('Ping Failed') self.connected = False if not self.connected: self.ws.wait_for_connect() self.connected = True def get_trace(self, frame, tb, w_code=None): frames = [] stack, current = self.get_stack(frame, tb) for i, (frame, lno) in enumerate(stack): code = frame.f_code filename = code.co_filename if filename == '<wdb>' and w_code: line = w_code else: checkcache(filename) line = getline(filename, lno, frame.f_globals) line = line and line.strip() startlnos = dis.findlinestarts(code) lastlineno = list(startlnos)[-1][1] frames.append({ 'file': filename, 'function': code.co_name, 'flno': code.co_firstlineno, 'llno': lastlineno, 'lno': lno, 'code': escape(line), 'level': i }) return stack, frames, current def interaction( self, frame, tb=None, exception='Wdb', exception_description='Set Trace'): if not self.ws: raise BdbQuit() try: self._interaction( frame, tb, exception, exception_description) except WsError: log.exception('Websocket Error during interaction. Starting again') self.handle_connection() self.interaction( frame, tb, exception, exception_description) def send(self, data): self.ws.send(data) def receive(self): message = None while not message: rv = self.ws.receive() if rv == 'CLOSED': raise WsError message = rv return message def _interaction( self, frame, tb, exception, exception_description): log.debug('Interaction for %r %r %r %r' % ( frame, tb, exception, exception_description)) stack, trace, current_index = self.get_trace(frame, tb) current = trace[current_index] locals_ = map(lambda x: x[0].f_locals, stack) if self.begun: self.send('Trace|%s' % dump({ 'trace': trace, 'cwd': os.getcwd() })) current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512(self.get_file(current_file)).hexdigest() })) else: self.begun = True while True: message = self.receive() if '|' in message: pipe = message.index('|') cmd = message[:pipe] data = message[pipe + 1:] else: cmd = message data = '' log.debug('Cmd %s #Data %d' % (cmd, len(data))) if cmd == 'Start': self.send('Init|%s' % dump({ 'cwd': os.getcwd() })) self.send('Title|%s' % dump({ 'title': exception, 'subtitle': exception_description })) self.send('Trace|%s' % dump({ 'trace': trace })) current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512(self.get_file(current_file)).hexdigest() })) elif cmd == 'Select': current_index = int(data) current = trace[current_index] current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512(self.get_file(current_file)).hexdigest() })) elif cmd == 'File': current_file = current['file'] self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current_file), 'file': self.get_file(current_file), 'name': current_file, 'sha512': sha512(self.get_file(current_file)).hexdigest() })) elif cmd == 'NoFile': self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current['file']) })) elif cmd == 'Inspect': try: thing = reverse_id(int(data)) except: continue self.send('Dump|%s' % dump({ 'for': escape(repr(thing)), 'val': dmp(thing)})) elif cmd == 'Dump': globals_ = dict(stack[current_index][0].f_globals) try: thing = eval(data, globals_, locals_[current_index]) except: continue self.send('Dump|%s' % dump({ 'for': escape(repr(thing)), 'val': dmp(thing)})) elif cmd == 'Trace': self.send('Trace|%s' % dump({ 'trace': trace })) elif cmd == 'Eval': globals_ = dict(stack[current_index][0].f_globals) # Hack for function scope eval globals_.update(locals_[current_index]) globals_.setdefault('_pprint', pprint) globals_.setdefault('_dump', dmp) with capture_output() as (out, err): try: compiled_code = compile(data, '<stdin>', 'single') exec compiled_code in globals_, locals_[current_index] except Exception: type_, value, tb = exc_info() print '%s: %s' % (type_.__name__, str(value)) self.send('Print|%s' % dump({ 'result': escape('\n'.join(out) + '\n'.join(err)) })) elif cmd == 'Ping': self.send('Pong') elif cmd == 'Step': if hasattr(self, 'botframe'): self.set_step() break elif cmd == 'Next': if hasattr(self, 'botframe'): self.set_next(stack[current_index][0]) break elif cmd == 'Continue': if hasattr(self, 'botframe'): self.set_continue() break elif cmd == 'Return': if hasattr(self, 'botframe'): self.set_return(stack[current_index][0]) break elif cmd == 'Until': if hasattr(self, 'botframe'): self.set_until(stack[current_index][0]) break elif cmd in ('TBreak', 'Break'): if ':' in data: fn, lno = data.split(':') else: fn, lno = current['file'], data cond = None if ',' in lno: lno, cond = lno.split(',') cond = cond.lstrip() lno = int(lno) rv = self.set_break(fn, lno, int(cmd == 'TBreak'), cond) if rv is not None: for path in sys.path: rv = self.set_break( os.path.join(path, fn), lno, int(cmd == 'TBreak'), cond) if rv is None: break if rv is None: log.info('Break set at %s:%d [%s]' % (fn, lno, rv)) if fn == current['file']: self.send('BreakSet|%s' % dump({ 'lno': lno, 'cond': cond })) else: self.send('BreakSet|%s' % dump({})) else: self.send('Log|%s' % dump({ 'message': rv })) elif cmd == 'Unbreak': lno = int(data) current_file = current['file'] log.info('Break unset at %s:%d' % (current_file, lno)) self.clear_break(current_file, lno) self.send('BreakUnset|%s' % dump({'lno': lno})) elif cmd == 'Jump': lno = int(data) if current_index != len(trace) - 1: log.error('Must be at bottom frame') continue try: stack[current_index][0].f_lineno = lno except ValueError: log.error('Jump failed') continue trace[current_index]['lno'] = lno self.send('Trace|%s' % dump({ 'trace': trace })) self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current['file']) })) elif cmd == 'Complete': current_file = current['file'] file_ = self.get_file(current_file, False).decode('utf-8') lines = file_.split(u'\n') lno = trace[current_index]['lno'] line_before = lines[lno - 1] indent = len(line_before) - len(line_before.lstrip()) segments = data.split(u'\n') for segment in reversed(segments): line = u' ' * indent + segment lines.insert(lno - 1, line) script = Script( u'\n'.join(lines), lno - 1 + len(segments), len(segments[-1]) + indent, '') try: completions = script.complete() except: self.send('Log|%s' % dump({ 'message': 'Completion failed for %s' % '\n'.join(reversed(segments)) })) else: fun = script.get_in_function_call() self.send('Suggest|%s' % dump({ 'params': { 'params': [p.get_code().replace('\n', '') for p in fun.params], 'index': fun.index, 'module': fun.module.path, 'call_name': fun.call_name} if fun else None, 'completions': [{ 'base': comp.word[ :len(comp.word) - len(comp.complete)], 'complete': comp.complete, 'description': comp.description } for comp in completions if comp.word.endswith( comp.complete)] })) elif cmd == 'Quit': if hasattr(self, 'botframe'): self.set_continue() raise BdbQuit() break else: log.warn('Unknown command %s' % cmd) def user_call(self, frame, argument_list): """This method is called when there is the remote possibility that we ever need to stop in this function.""" if self.stop_here(frame): fun = frame.f_code.co_name log.info('Calling: %r' % fun) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__call__', 'val': fun})) self.interaction(frame) def user_line(self, frame): """This function is called when we stop or break at this line.""" log.info('Stopping at line %r:%d' % ( frame.f_code.co_filename, frame.f_lineno)) self.handle_connection() log.debug('User Line Interaction for %r' % frame) self.interaction(frame) def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" frame.f_locals['__return__'] = return_value log.info('Returning from %r with value: %r' % ( frame.f_code.co_name, return_value)) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__return__', 'val': return_value })) self.interaction(frame) def user_exception(self, frame, exc_info): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" log.error('Exception', exc_info=exc_info) type_, value, tb = exc_info frame.f_locals['__exception__'] = type_, value exception = type_.__name__ exception_description = str(value) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__exception__', 'val': '%s: %s' % ( exception, exception_description)})) if not self.begun: frame = None self.interaction(frame, tb, exception, exception_description) def do_clear(self, arg): log.info('Closing %r' % arg) self.clear_bpbynumber(arg) def dispatch_exception(self, frame, arg): self.user_exception(frame, arg) if self.quitting: raise BdbQuit return self.trace_dispatch def recursive(self, g, l): # Inspect curent debugger vars through pdb sys.settrace(None) from pdb import Pdb p = Pdb() sys.call_tracing(p.run, ('1/0', g, l)) sys.settrace(self.trace_dispatch) self.lastcmd = p.lastcmd
class WdbRequest(object, Bdb): """Wdb debugger main class""" __metaclass__ = MetaWdbRequest def __init__(self, ports, skip=None): MetaWdbRequest._last_inst = self self.obj_cache = {} try: Bdb.__init__(self, skip=skip) except TypeError: Bdb.__init__(self) self.begun = False self.connected = False self.make_web_socket(ports) breaks_per_file_lno = Breakpoint.bplist.values() for bps in breaks_per_file_lno: breaks = list(bps) for bp in breaks: args = bp.file, bp.line, bp.temporary, bp.cond self.set_break(*args) log.info('Resetting break %s' % repr(args)) def safe_repr(self, obj): try: return repr(obj) except Exception as e: return '??? Broken repr (%s: %s)' % (type(e).__name__, e) def safe_better_repr(self, obj): try: rv = self.better_repr(obj) except Exception: rv = None if rv: return rv self.obj_cache[id(obj)] = obj return '<a href="%d" class="inspect">%s</a>' % ( id(obj), escape(repr(obj))) def better_repr(self, obj): if isinstance(obj, dict): if type(obj) != dict: dict_repr = type(obj).__name__ + '({' closer = '})' else: dict_repr = '{' closer = '}' dict_repr += ', '.join([ self.safe_repr(key) + ': ' + self.safe_better_repr(val) for key, val in obj.items()]) dict_repr += closer return dict_repr if any([ isinstance(obj, list), isinstance(obj, set), isinstance(obj, tuple)]): if type(obj) == list: iter_repr = '[' closer = ']' elif type(obj) == set: iter_repr = '{' closer = '}' elif type(obj) == tuple: iter_repr = '(' closer = ')' else: iter_repr = escape(obj.__class__.__name__) + '([' closer = '])' iter_repr += ', '.join([self.safe_better_repr(val) for val in obj]) iter_repr += closer return iter_repr @contextmanager def capture_output(self, with_hook=True): self.hooked = '' def display_hook(obj): # That's some dirty hack self.hooked += self.safe_better_repr(obj) stdout, stderr = sys.stdout, sys.stderr if with_hook: d_hook = sys.displayhook sys.displayhook = display_hook sys.stdout, sys.stderr = StringIO(), StringIO() out, err = [], [] try: yield out, err finally: out.extend(sys.stdout.getvalue().splitlines()) err.extend(sys.stderr.getvalue().splitlines()) if with_hook: sys.displayhook = d_hook sys.stdout, sys.stderr = stdout, stderr def dmp(self, thing): def safe_getattr(key): try: return getattr(thing, key) except Exception as e: return 'Error getting attr "%s" from "%s" (%s: %s)' % ( key, thing, type(e).__name__, e) return dict( (escape(key), { 'val': self.safe_better_repr(safe_getattr(key)), 'type': type(safe_getattr(key)).__name__ }) for key in dir(thing) ) def make_web_socket(self, ports): log.info('Creating WebSocket') for port in ports: self.ws = WebSocket('0.0.0.0', port) if self.ws.status == 'OK': return time.sleep(.100) raise WsError('No port could be opened') def wsgi_trace(self, app, environ, start_response): def wsgi_with_trace(environ, start_response): self.quitting = 0 self.begun = False self.reset() frame = sys._getframe() while frame: frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back self.stopframe = sys._getframe().f_back self.stoplineno = -1 sys.settrace(self.trace_dispatch) try: appiter = app(environ, start_response) except BdbQuit: sys.settrace(None) start_response('200 OK', [('Content-Type', 'text/html')]) yield '<h1>BdbQuit</h1><p>Wdb was interrupted</p>' else: for item in appiter: yield item hasattr(appiter, 'close') and appiter.close() sys.settrace(None) self.ws.force_close() return wsgi_with_trace(environ, start_response) def get_file(self, filename, html_escape=True): checkcache(filename) file_ = ''.join(getlines(filename)) if not html_escape: return file_ return escape(file_) def handle_connection(self): if self.connected: try: self.send('Ping') except: log.exception('Ping Failed') self.connected = False if not self.connected: self.ws.wait_for_connect() self.connected = True def get_trace(self, frame, tb, w_code=None): frames = [] stack, current = self.get_stack(frame, tb) for i, (frame, lno) in enumerate(stack): code = frame.f_code filename = code.co_filename if filename == '<wdb>' and w_code: line = w_code else: checkcache(filename) line = getline(filename, lno, frame.f_globals) line = line and line.strip() startlnos = dis.findlinestarts(code) lastlineno = list(startlnos)[-1][1] frames.append({ 'file': filename, 'function': code.co_name, 'flno': code.co_firstlineno, 'llno': lastlineno, 'lno': lno, 'code': escape(line), 'level': i }) return stack, frames, current def handle_exc(self): type_, value = exc_info()[:2] return '<a title="%s">%s: %s</a>' % ( escape(traceback.format_exc().replace('"', '\'')), escape(type_.__name__), escape(str(value))) def interaction( self, frame, tb=None, exception='Wdb', exception_description='Set Trace'): if not self.ws: raise BdbQuit() try: self._interaction( frame, tb, exception, exception_description) except WsError: log.exception('Websocket Error during interaction. Starting again') self.handle_connection() self.interaction( frame, tb, exception, exception_description) def send(self, data): self.ws.send(data) def receive(self): message = None while not message: rv = self.ws.receive() if rv == 'CLOSED': raise WsError message = rv return message def _interaction( self, frame, tb, exception, exception_description): log.debug('Interaction for %r %r %r %r' % ( frame, tb, exception, exception_description)) stack, trace, current_index = self.get_trace(frame, tb) current = trace[current_index] locals_ = map(lambda x: x[0].f_locals, stack) if self.begun: self.send('Trace|%s' % dump({ 'trace': trace, 'cwd': os.getcwd() })) current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512(self.get_file(current_file)).hexdigest() })) else: self.begun = True while True: try: message = self.receive() if '|' in message: pipe = message.index('|') cmd = message[:pipe] data = message[pipe + 1:] else: cmd = message data = '' def fail(title=None, message=None): if message is None: message = self.handle_exc() else: message = escape(message) self.send('Echo|%s' % dump({ 'for': escape(title or '%s failed' % cmd), 'val': message })) log.debug('Cmd %s #Data %d' % (cmd, len(data))) if cmd == 'Start': self.send('Init|%s' % dump({ 'cwd': os.getcwd() })) self.send('Title|%s' % dump({ 'title': exception, 'subtitle': exception_description })) self.send('Trace|%s' % dump({ 'trace': trace })) current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512( self.get_file(current_file)).hexdigest() })) elif cmd == 'Select': current_index = int(data) current = trace[current_index] current_file = current['file'] self.send('Check|%s' % dump({ 'name': current_file, 'sha512': sha512( self.get_file(current_file)).hexdigest() })) elif cmd == 'File': current_file = current['file'] self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current_file), 'file': self.get_file(current_file), 'name': current_file, 'sha512': sha512( self.get_file(current_file)).hexdigest() })) elif cmd == 'NoFile': self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current['file']) })) elif cmd == 'Inspect': try: thing = self.obj_cache.get(int(data)) except Exception: fail() continue self.send('Dump|%s' % dump({ 'for': escape(repr(thing)), 'val': self.dmp(thing)})) elif cmd == 'Dump': globals_ = dict(stack[current_index][0].f_globals) try: thing = eval(data, globals_, locals_[current_index]) except Exception: fail() continue else: self.send('Dump|%s' % dump({ 'for': escape(u'%s ⟶ %s ' % (data, repr(thing))), 'val': self.dmp(thing)})) elif cmd == 'Trace': self.send('Trace|%s' % dump({ 'trace': trace })) elif cmd == 'Eval': redir = None raw_data = data = data.strip() if '!>' in data: data, redir = data.split('!>') data = data.strip() redir = redir.strip() elif data.startswith('!<'): filename = data[2:].strip() try: with open(filename, 'r') as f: data = f.read() except Exception: fail('Unable to read from file %s' % filename) continue globals_ = dict(stack[current_index][0].f_globals) # Hack for function scope eval globals_.update(locals_[current_index]) globals_.setdefault('_pprint', pprint) with self.capture_output( with_hook=redir is None) as (out, err): try: compiled_code = compile(data, '<stdin>', 'single') l = locals_[current_index] exec compiled_code in globals_, l except Exception: self.hooked = self.handle_exc() if redir: try: with open(redir, 'w') as f: f.write('\n'.join(out) + '\n'.join(err) + '\n') except Exception: fail('Unable to write to file %s' % redir) continue self.send('Print|%s' % dump({ 'for': escape(raw_data), 'result': escape('Written to file %s' % redir) })) else: self.send('Print|%s' % dump({ 'for': escape(raw_data), 'result': self.hooked + escape( '\n'.join(out) + '\n'.join(err)) })) elif cmd == 'Ping': self.send('Pong') elif cmd == 'Step': if hasattr(self, 'botframe'): self.set_step() break elif cmd == 'Next': if hasattr(self, 'botframe'): self.set_next(stack[current_index][0]) break elif cmd == 'Continue': if hasattr(self, 'botframe'): self.set_continue() break elif cmd == 'Return': if hasattr(self, 'botframe'): self.set_return(stack[current_index][0]) break elif cmd == 'Until': if hasattr(self, 'botframe'): self.set_until(stack[current_index][0]) break elif cmd in ('TBreak', 'Break'): break_fail = lambda x: fail( 'Break on %s failed' % data, message=x) if ':' in data: fn, lno = data.split(':') else: fn, lno = current['file'], data cond = None if ',' in lno: lno, cond = lno.split(',') cond = cond.lstrip() try: lno = int(lno) except: break_fail( 'Wrong breakpoint format must be ' '[file:]lno[,cond].') continue line = getline( fn, lno, stack[current_index][0].f_globals) if not line: break_fail('Line does not exist') continue line = line.strip() if ((not line or (line[0] == '#') or (line[:3] == '"""') or line[:3] == "'''")): break_fail('Blank line or comment') continue first_rv = rv = self.set_break( fn, lno, int(cmd == 'TBreak'), cond) if rv is not None: for path in sys.path: rv = self.set_break( os.path.join(path, fn), lno, int(cmd == 'TBreak'), cond) if rv is None: break if rv is None: log.info('Break set at %s:%d [%s]' % (fn, lno, rv)) if fn == current['file']: self.send('BreakSet|%s' % dump({ 'lno': lno, 'cond': cond })) else: self.send('BreakSet|%s' % dump({})) else: break_fail(first_rv) elif cmd == 'Unbreak': lno = int(data) current_file = current['file'] log.info('Break unset at %s:%d' % (current_file, lno)) self.clear_break(current_file, lno) self.send('BreakUnset|%s' % dump({'lno': lno})) elif cmd == 'Jump': lno = int(data) if current_index != len(trace) - 1: log.error('Must be at bottom frame') continue try: stack[current_index][0].f_lineno = lno except ValueError: fail() continue trace[current_index]['lno'] = lno self.send('Trace|%s' % dump({ 'trace': trace })) self.send('Select|%s' % dump({ 'frame': current, 'breaks': self.get_file_breaks(current['file']) })) elif cmd == 'Complete': current_file = current['file'] file_ = self.get_file(current_file, False).decode('utf-8') lines = file_.split(u'\n') lno = trace[current_index]['lno'] line_before = lines[lno - 1] indent = len(line_before) - len(line_before.lstrip()) segments = data.split(u'\n') for segment in reversed(segments): line = u' ' * indent + segment lines.insert(lno - 1, line) script = Script( u'\n'.join(lines), lno - 1 + len(segments), len(segments[-1]) + indent, '') try: completions = script.complete() except: log.exception('Completion failed') self.send('Log|%s' % dump({ 'message': 'Completion failed for %s' % '\n'.join(reversed(segments)) })) else: fun = script.get_in_function_call() self.send('Suggest|%s' % dump({ 'params': { 'params': [p.get_code().replace('\n', '') for p in fun.params], 'index': fun.index, 'module': fun.module.path, 'call_name': fun.call_name} if fun else None, 'completions': [{ 'base': comp.word[ :len(comp.word) - len(comp.complete)], 'complete': comp.complete, 'description': comp.description } for comp in completions if comp.word.endswith( comp.complete)] })) elif cmd == 'Quit': if hasattr(self, 'botframe'): self.set_continue() raise BdbQuit() break else: log.warn('Unknown command %s' % cmd) except BdbQuit: raise except Exception: try: exc = self.handle_exc() type_, value = exc_info()[:2] link = ('<a href="https://github.com/Kozea/wdb/issues/new?' 'title=%s&body=%s&labels=defect" class="nogood">' 'Please click here to report it on Github</a>') % ( quote('%s: %s' % (type_.__name__, str(value))), quote('```\n%s\n```\n' % traceback.format_exc())) self.send('Echo|%s' % dump({ 'for': escape('Error in Wdb, this is bad'), 'val': exc + '<br>' + link })) except: self.send('Echo|%s' % dump({ 'for': escape('Too many errors'), 'val': escape("Don't really know what to say. " "Maybe it will work tomorrow.") })) continue def user_call(self, frame, argument_list): """This method is called when there is the remote possibility that we ever need to stop in this function.""" if self.stop_here(frame): fun = frame.f_code.co_name log.info('Calling: %r' % fun) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__call__', 'val': fun})) self.interaction(frame) def user_line(self, frame): """This function is called when we stop or break at this line.""" log.info('Stopping at line %r:%d' % ( frame.f_code.co_filename, frame.f_lineno)) self.handle_connection() log.debug('User Line Interaction for %r' % frame) self.interaction(frame) def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" frame.f_locals['__return__'] = return_value log.info('Returning from %r with value: %r' % ( frame.f_code.co_name, return_value)) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__return__', 'val': return_value })) self.interaction(frame) def user_exception(self, frame, exc_info): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" log.error('Exception', exc_info=exc_info) type_, value, tb = exc_info frame.f_locals['__exception__'] = type_, value exception = type_.__name__ exception_description = str(value) self.handle_connection() self.send('Echo|%s' % dump({ 'for': '__exception__', 'val': escape('%s: %s') % ( exception, exception_description)})) if not self.begun: frame = None self.interaction(frame, tb, exception, exception_description) def do_clear(self, arg): log.info('Closing %r' % arg) self.clear_bpbynumber(arg) def dispatch_exception(self, frame, arg): self.user_exception(frame, arg) if self.quitting: raise BdbQuit return self.trace_dispatch def recursive(self, g, l): # Inspect curent debugger vars through pdb sys.settrace(None) from pdb import Pdb p = Pdb() sys.call_tracing(p.run, ('1/0', g, l)) sys.settrace(self.trace_dispatch) self.lastcmd = p.lastcmd