source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/dbg.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 37.7 KB
Line 
1#!/usr/bin/env python3
2# coding:utf-8
3
4"Queues(Pipe)-based independent remote client-server Python Debugger (new-py3)"
5
6from __future__ import print_function
7
8__author__ = "Mariano Reingart (reingart@gmail.com)"
9__copyright__ = "Copyright (C) 2011 Mariano Reingart"
10__license__ = "LGPL 3.0"
11__version__ = "1.5.2"
12
13# remote debugger queue-based (jsonrpc-like interface):
14# - bidirectional communication (request - response calls in both ways)
15# - request with id == null is a notification (do not send a response)
16# - request with a value for id is a normal call, wait response
17# based on idle, inspired by pythonwin implementation, taken many code from pdb
18
19import bdb
20import inspect
21import linecache
22import os
23import sys
24import traceback
25import cmd
26import pydoc
27import threading
28import collections
29
30
31# Speed Ups: global variables
32breaks = []
33
34
35class Qdb(bdb.Bdb):
36    "Qdb Debugger Backend"
37
38    def __init__(self, pipe, redirect_stdio=True, allow_interruptions=False,
39                 use_speedups=True, skip=[__name__]):
40        global breaks
41        kwargs = {}
42        if sys.version_info > (2, 7):
43            kwargs['skip'] = skip
44        bdb.Bdb.__init__(self, **kwargs)
45        self.frame = None
46        self.i = 1  # sequential RPC call id
47        self.waiting = False
48        self.pipe = pipe # for communication
49        self._wait_for_mainpyfile = False
50        self._wait_for_breakpoint = False
51        self.mainpyfile = ""
52        self._lineno = None     # last listed line numbre
53        # ignore filenames (avoid spurious interaction specially on py2)
54        self.ignore_files = [self.canonic(f) for f in (__file__, bdb.__file__)]
55        # replace system standard input and output (send them thru the pipe)
56        self.old_stdio = sys.stdin, sys.stdout, sys.stderr
57        if redirect_stdio:
58            sys.stdin = self
59            sys.stdout = self
60            sys.stderr = self
61        if allow_interruptions:
62            # fake breakpoint to prevent removing trace_dispatch on set_continue
63            self.breaks[None] = []
64        self.allow_interruptions = allow_interruptions
65        self.burst = 0          # do not send notifications ("burst" mode)
66        self.params = {}        # optional parameters for interaction
67       
68        # flags to reduce overhead (only stop at breakpoint or interrupt)
69        self.use_speedups = use_speedups
70        self.fast_continue = False
71
72    def pull_actions(self):
73        # receive a remote procedure call from the frontend:
74        # returns True if action processed
75        #         None when 'run' notification is received (see 'startup')
76        request = self.pipe.recv()
77        if request.get("method") == 'run':
78            return None
79        response = {'version': '1.1', 'id': request.get('id'),
80                    'result': None,
81                    'error': None}
82        try:
83            # dispatch message (JSON RPC like)
84            method = getattr(self, request['method'])
85            response['result'] = method.__call__(*request['args'],
86                                        **request.get('kwargs', {}))
87        except Exception as e:
88            response['error'] = {'code': 0, 'message': str(e)}
89        # send the result for normal method calls, not for notifications
90        if request.get('id'):
91            self.pipe.send(response)
92        return True
93
94    # Override Bdb methods
95
96    def trace_dispatch(self, frame, event, arg):
97        # check for non-interaction rpc (set_breakpoint, interrupt)
98        while self.allow_interruptions and self.pipe.poll():
99            self.pull_actions()
100            # check for non-interaction rpc (set_breakpoint, interrupt)
101            while self.pipe.poll():
102                self.pull_actions()
103        if (frame.f_code.co_filename, frame.f_lineno) not in breaks and \
104            self.fast_continue:
105            return self.trace_dispatch
106        # process the frame (see Bdb.trace_dispatch)
107        ##if self.fast_continue:
108        ##    return self.trace_dispatch
109        if self.quitting:
110            return # None
111        if event == 'line':
112            return self.dispatch_line(frame)
113        if event == 'call':
114            return self.dispatch_call(frame, arg)
115        if event == 'return':
116            return self.dispatch_return(frame, arg)
117        if event == 'exception':
118            return self.dispatch_exception(frame, arg)
119        return self.trace_dispatch
120
121    def user_call(self, frame, argument_list):
122        """This method is called when there is the remote possibility
123        that we ever need to stop in this function."""
124        if self._wait_for_mainpyfile or self._wait_for_breakpoint:
125            return
126        if self.stop_here(frame):
127            self.interaction(frame)
128   
129    def user_line(self, frame):
130        """This function is called when we stop or break at this line."""
131        if self._wait_for_mainpyfile:
132            if (not self.canonic(frame.f_code.co_filename).startswith(self.mainpyfile)
133                or frame.f_lineno<= 0):
134                return
135            self._wait_for_mainpyfile = 0
136        if self._wait_for_breakpoint:
137            if not self.break_here(frame):
138                return
139            self._wait_for_breakpoint = 0
140        self.interaction(frame)
141
142    def user_exception(self, frame, info):
143        """This function is called if an exception occurs,
144        but only if we are to stop at or just below this level."""
145        if self._wait_for_mainpyfile or self._wait_for_breakpoint:
146            return
147        extype, exvalue, trace = info
148        # pre-process stack trace as it isn't pickeable (cannot be sent pure)
149        msg = ''.join(traceback.format_exception(extype, exvalue, trace))
150        # in python3.5, convert FrameSummary to tuples (py2.7+ compatibility)
151        tb = [tuple(fs) for fs in traceback.extract_tb(trace)]
152        title = traceback.format_exception_only(extype, exvalue)[0]
153        # send an Exception notification
154        msg = {'method': 'exception',
155               'args': (title, extype.__name__, repr(exvalue), tb, msg),
156               'id': None}
157        self.pipe.send(msg)
158        self.interaction(frame)
159
160    def run(self, code, interp=None, *args, **kwargs):
161        try:
162            return bdb.Bdb.run(self, code, *args, **kwargs)
163        finally:
164            pass
165
166    def runcall(self, function, interp=None, *args, **kwargs):
167        try:
168            self.interp = interp
169            return bdb.Bdb.runcall(self, function, *args, **kwargs)
170        finally:
171            pass
172
173    def _runscript(self, filename):
174        # The script has to run in __main__ namespace (clear it)
175        import __main__
176        import imp
177        filename = os.path.abspath(filename)
178        __main__.__dict__.clear()
179        __main__.__dict__.update({"__name__"    : "__main__",
180                                  "__file__"    : filename,
181                                  "__builtins__": __builtins__,
182                                  "imp"         : imp,          # need for run
183                                 })
184
185        # avoid stopping before we reach the main script
186        self._wait_for_mainpyfile = 1
187        self.mainpyfile = self.canonic(filename)
188        self._user_requested_quit = 0
189        if sys.version_info>(3,0):
190            statement = 'imp.load_source("__main__", "%s")' % filename
191        else:
192            statement = 'execfile(%r)' % filename
193        self.startup()
194        self.run(statement)
195
196    def startup(self):
197        "Notify and wait frontend to set initial params and breakpoints"
198        # send some useful info to identify session
199        thread = threading.current_thread()
200        # get the caller module filename
201        frame = sys._getframe()
202        fn = self.canonic(frame.f_code.co_filename)
203        while frame.f_back and self.canonic(frame.f_code.co_filename) == fn:
204            frame = frame.f_back
205        args = [__version__, os.getpid(), thread.name, " ".join(sys.argv),
206                frame.f_code.co_filename]
207        self.pipe.send({'method': 'startup', 'args': args})
208        while self.pull_actions() is not None:
209            pass
210
211    # General interaction function
212
213    def interaction(self, frame):
214        # chache frame locals to ensure that modifications are not overwritten
215        self.frame_locals = frame and frame.f_locals or {}
216        # extract current filename and line number
217        code, lineno = frame.f_code, frame.f_lineno
218        filename = self.canonic(code.co_filename)
219        basename = os.path.basename(filename)
220        # check if interaction should be ignored (i.e. debug modules internals)
221        if filename in self.ignore_files:
222            return
223        message = "%s:%s" % (basename, lineno)
224        if code.co_name != "?":
225            message = "%s: %s()" % (message, code.co_name)
226
227        # wait user events
228        self.waiting = True   
229        self.frame = frame
230        try:
231            while self.waiting:
232                #  sync_source_line()
233                if frame and filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
234                    line = linecache.getline(filename, self.frame.f_lineno,
235                                             self.frame.f_globals)
236                else:
237                    line = ""
238                # send the notification (debug event) - DOESN'T WAIT RESPONSE
239                self.burst -= 1
240                if self.burst < 0:
241                    kwargs = {}
242                    if self.params.get('call_stack'):
243                        kwargs['call_stack'] = self.do_where()
244                    if self.params.get('environment'):
245                        kwargs['environment'] = self.do_environment()
246                    self.pipe.send({'method': 'interaction', 'id': None,
247                                'args': (filename, self.frame.f_lineno, line),
248                                'kwargs': kwargs})
249
250                self.pull_actions()
251        finally:
252            self.waiting = False
253        self.frame = None
254
255    def do_debug(self, mainpyfile=None, wait_breakpoint=1):
256        self.reset()
257        if not wait_breakpoint or mainpyfile:
258            self._wait_for_mainpyfile = 1
259            if not mainpyfile:
260                frame = sys._getframe().f_back
261                mainpyfile = frame.f_code.co_filename
262            self.mainpyfile = self.canonic(mainpyfile)
263        self._wait_for_breakpoint = wait_breakpoint
264        sys.settrace(self.trace_dispatch)
265
266    def set_trace(self, frame=None):
267        # start debugger interaction immediatelly
268        if frame is None:
269            frame = sys._getframe().f_back
270        self._wait_for_mainpyfile = frame.f_code.co_filename
271        self._wait_for_breakpoint = 0
272        # reinitialize debugger internal settings
273        self.fast_continue = False
274        bdb.Bdb.set_trace(self, frame)
275
276    # Command definitions, called by interaction()
277
278    def do_continue(self):
279        self.set_continue()
280        self.waiting = False
281        self.fast_continue = self.use_speedups
282
283    def do_step(self):
284        self.set_step()
285        self.waiting = False
286        self.fast_continue = False
287
288    def do_return(self):
289        self.set_return(self.frame)
290        self.waiting = False
291        self.fast_continue = False
292
293    def do_next(self):
294        self.set_next(self.frame)
295        self.waiting = False
296        self.fast_continue = False
297
298    def interrupt(self):
299        self.set_trace()
300        self.fast_continue = False
301
302    def do_quit(self):
303        self.set_quit()
304        self.waiting = False
305        self.fast_continue = False
306
307    def do_jump(self, lineno):
308        arg = int(lineno)
309        try:
310            self.frame.f_lineno = arg
311        except ValueError as e:
312            return str(e)
313
314    def do_list(self, arg):
315        last = None
316        if arg:
317            if isinstance(arg, tuple):
318                first, last = arg
319            else:
320                first = arg
321        elif not self._lineno:
322            first = max(1, self.frame.f_lineno - 5)                       
323        else:
324            first = self._lineno + 1
325        if last is None:
326            last = first + 10
327        filename = self.frame.f_code.co_filename
328        breaklist = self.get_file_breaks(filename)
329        lines = []
330        for lineno in range(first, last+1):
331            line = linecache.getline(filename, lineno,
332                                     self.frame.f_globals)
333            if not line:
334                lines.append((filename, lineno, '', "", "<EOF>\n"))
335                break
336            else:
337                breakpoint = "B" if lineno in breaklist else ""
338                current = "->" if self.frame.f_lineno == lineno else ""
339                lines.append((filename, lineno, breakpoint, current, line))
340                self._lineno = lineno
341        return lines
342
343    def do_read(self, filename):
344        return open(filename, "Ur").read()
345
346    def do_set_breakpoint(self, filename, lineno, temporary=0, cond=None):
347        global breaks   # list for speedups!
348        breaks.append((filename.replace("\\", "/"), int(lineno)))
349        return self.set_break(filename, int(lineno), temporary, cond)
350
351    def do_list_breakpoint(self):
352        breaks = []
353        if self.breaks:  # There's at least one
354            for bp in bdb.Breakpoint.bpbynumber:
355                if bp:
356                    breaks.append((bp.number, bp.file, bp.line,
357                        bp.temporary, bp.enabled, bp.hits, bp.cond, ))
358        return breaks
359
360    def do_clear_breakpoint(self, filename, lineno):
361        self.clear_break(filename, lineno)
362
363    def do_clear_file_breakpoints(self, filename):
364        self.clear_all_file_breaks(filename)
365
366    def do_clear(self, arg):
367        # required by BDB to remove temp breakpoints!
368        err = self.clear_bpbynumber(arg)
369        if err:
370            print('*** DO_CLEAR failed', err)
371
372    def do_eval(self, arg, safe=True):
373        if self.frame:
374            ret = eval(arg, self.frame.f_globals,
375                        self.frame_locals)
376        else:
377            ret = RPCError("No current frame available to eval")
378        if safe:
379            ret = pydoc.cram(repr(ret), 255)
380        return ret
381
382    def do_exec(self, arg, safe=True):
383        if not self.frame:
384            ret = RPCError("No current frame available to exec")
385        else:
386            locals = self.frame_locals
387            globals = self.frame.f_globals
388            code = compile(arg + '\n', '<stdin>', 'single')
389            save_displayhook = sys.displayhook
390            self.displayhook_value = None
391            try:
392                sys.displayhook = self.displayhook
393                exec(code, globals, locals)
394                ret = self.displayhook_value
395            finally:
396                sys.displayhook = save_displayhook
397        if safe:
398            ret = pydoc.cram(repr(ret), 255)
399        return ret
400
401    def do_where(self):
402        "print_stack_trace"
403        stack, curindex = self.get_stack(self.frame, None)
404        lines = []
405        for frame, lineno in stack:
406            filename = frame.f_code.co_filename
407            line = linecache.getline(filename, lineno)
408            lines.append((filename, lineno, "", "", line, ))
409        return lines
410
411    def do_environment(self):
412        "return current frame local and global environment"
413        env = {'locals': {}, 'globals': {}}
414        # converts the frame global and locals to a short text representation:
415        if self.frame:
416            for scope, max_length, vars in (
417                    ("locals", 255, list(self.frame_locals.items())),
418                    ("globals", 20, list(self.frame.f_globals.items())), ):
419                for (name, value) in vars:
420                    try:
421                        short_repr = pydoc.cram(repr(value), max_length)                   
422                    except Exception as e:
423                        # some objects cannot be represented...
424                        short_repr = "**exception** %s" % repr(e)
425                    env[scope][name] = (short_repr, repr(type(value)))
426        return env
427
428    def get_autocomplete_list(self, expression):
429        "Return list of auto-completion options for expression"
430        try:
431            obj = self.do_eval(expression, safe=False)
432        except:
433            return []
434        else:
435            return dir(obj)
436   
437    def get_call_tip(self, expression):
438        "Return list of auto-completion options for expression"
439        try:
440            obj = self.do_eval(expression)
441        except Exception as e:
442            return ('', '', str(e))
443        else:
444            name = ''
445            try:
446                name = obj.__name__
447            except AttributeError:
448                pass
449            argspec = ''
450            drop_self = 0
451            f = None
452            try:
453                if inspect.isbuiltin(obj):
454                    pass
455                elif inspect.ismethod(obj):
456                    # Get the function from the object
457                    f = obj.__func__
458                    drop_self = 1
459                elif inspect.isclass(obj):
460                    # Get the __init__ method function for the class.
461                    if hasattr(obj, '__init__'):
462                        f = obj.__init__.__func__
463                    else:
464                        for base in object.__bases__:
465                            if hasattr(base, '__init__'):
466                                f = base.__init__.__func__
467                                break
468                    if f is not None:
469                        drop_self = 1
470                elif isinstance(obj, collections.Callable):
471                    # use the obj as a function by default
472                    f = obj
473                    # Get the __call__ method instead.
474                    f = obj.__call__.__func__
475                    drop_self = 0
476            except AttributeError:
477                pass
478            if f:
479                argspec = inspect.formatargspec(*inspect.getargspec(f))
480            doc = ''
481            if isinstance(obj, collections.Callable):
482                try:
483                    doc = inspect.getdoc(obj)
484                except:
485                    pass
486            return (name, argspec[1:-1], doc.strip())
487
488    def set_burst(self, val):
489        "Set burst mode -multiple command count- (shut up notifications)"
490        self.burst = val
491
492    def set_params(self, params):
493        "Set parameters for interaction"
494        self.params.update(params)
495
496    def displayhook(self, obj):
497        """Custom displayhook for the do_exec which prevents
498        assignment of the _ variable in the builtins.
499        """
500        self.displayhook_value = repr(obj)
501
502    def reset(self):
503        bdb.Bdb.reset(self)
504        self.waiting = False
505        self.frame = None
506
507    def post_mortem(self, info=None):
508        "Debug an un-handled python exception"
509        # check if post mortem mode is enabled:
510        if not self.params.get('postmortem', True):
511            return
512        # handling the default
513        if info is None:
514            # sys.exc_info() returns (type, value, traceback) if an exception is
515            # being handled, otherwise it returns None
516            info = sys.exc_info()
517        # extract the traceback object:
518        t = info[2]
519        if t is None:
520            raise ValueError("A valid traceback must be passed if no "
521                             "exception is being handled")
522        self.reset()
523        # get last frame:
524        while t is not None:
525            frame = t.tb_frame
526            t = t.tb_next
527            code, lineno = frame.f_code, frame.f_lineno
528            filename = code.co_filename
529            line = linecache.getline(filename, lineno)
530            #(filename, lineno, "", current, line, )}
531        # SyntaxError doesn't execute even one line, so avoid mainpyfile check
532        self._wait_for_mainpyfile = False
533        # send exception information & request interaction
534        self.user_exception(frame, info)
535
536    def ping(self):
537        "Minimal method to test that the pipe (connection) is alive"
538        try:
539            # get some non-trivial data to compare:
540            args = (id(object()), )
541            msg = {'method': 'ping', 'args': args, 'id': None}
542            self.pipe.send(msg)
543            msg = self.pipe.recv()
544            # check if data received is ok (alive and synchonized!)
545            return msg['result'] == args
546        except (IOError, EOFError):
547            return None
548       
549    # console file-like object emulation
550    def readline(self):
551        "Replacement for stdin.readline()"
552        msg = {'method': 'readline', 'args': (), 'id': self.i}
553        self.pipe.send(msg)
554        msg = self.pipe.recv()
555        self.i += 1
556        return msg['result']
557
558    def readlines(self):
559        "Replacement for stdin.readlines()"
560        lines = []
561        while lines[-1:] != ['\n']:
562            lines.append(self.readline())
563        return lines
564
565    def write(self, text):
566        "Replacement for stdout.write()"
567        msg = {'method': 'write', 'args': (text, ), 'id': None}
568        self.pipe.send(msg)
569       
570    def writelines(self, l):
571        list(map(self.write, l))
572
573    def flush(self):
574        pass
575
576    def isatty(self):
577        return 0
578
579    def encoding(self):
580        return None  # use default, 'utf-8' should be better...
581
582    def close(self):
583        # revert redirections and close connection
584        if sys:
585            sys.stdin, sys.stdout, sys.stderr = self.old_stdio
586        try:
587            self.pipe.close()
588        except:
589            pass
590
591    def __del__(self):
592        self.close()
593
594
595class QueuePipe(object):
596    "Simulated pipe for threads (using two queues)"
597   
598    def __init__(self, name, in_queue, out_queue):
599        self.__name = name
600        self.in_queue = in_queue
601        self.out_queue = out_queue
602
603    def send(self, data):
604        self.out_queue.put(data, block=True)
605
606    def recv(self, count=None, timeout=None):
607        data = self.in_queue.get(block=True, timeout=timeout)
608        return data
609
610    def poll(self, timeout=None):
611        return not self.in_queue.empty()
612
613    def close(self):
614        pass
615
616
617class RPCError(RuntimeError):
618    "Remote Error (not user exception)"
619    pass
620
621   
622class Frontend(object):
623    "Qdb generic Frontend interface"
624   
625    def __init__(self, pipe):
626        self.i = 1
627        self.info = ()
628        self.pipe = pipe
629        self.notifies = []
630        self.read_lock = threading.RLock()
631        self.write_lock = threading.RLock()
632
633    def recv(self):
634        self.read_lock.acquire()
635        try:
636            return self.pipe.recv()
637        finally:
638            self.read_lock.release()
639
640    def send(self, data):
641        self.write_lock.acquire()
642        try:
643            return self.pipe.send(data)
644        finally:
645            self.write_lock.release()
646
647    def startup(self, version, pid, thread_name, argv, filename):
648        self.info = (version, pid, thread_name, argv, filename)
649        self.send({'method': 'run', 'args': (), 'id': None})
650
651    def interaction(self, filename, lineno, line, *kwargs):
652        raise NotImplementedError
653   
654    def exception(self, title, extype, exvalue, trace, request):
655        "Show a user_exception"
656        raise NotImplementedError
657
658    def write(self, text):
659        "Console output (print)"
660        raise NotImplementedError
661   
662    def readline(self, text):
663        "Console input/rawinput"
664        raise NotImplementedError
665
666    def run(self):
667        "Main method dispatcher (infinite loop)"
668        if self.pipe:
669            if not self.notifies:
670                # wait for a message...
671                request = self.recv()
672            else:
673                # process an asyncronus notification received earlier
674                request = self.notifies.pop(0)
675            return self.process_message(request)
676   
677    def process_message(self, request):
678        if request:
679            result = None
680            if request.get("error"):
681                # it is not supposed to get an error here
682                # it should be raised by the method call
683                raise RPCError(res['error']['message'])
684            elif request.get('method') == 'interaction':
685                self.interaction(*request.get("args"), **request.get("kwargs"))
686            elif request.get('method') == 'startup':
687                self.startup(*request['args'])
688            elif request.get('method') == 'exception':
689                self.exception(*request['args'])
690            elif request.get('method') == 'write':
691                self.write(*request.get("args"))
692            elif request.get('method') == 'readline':
693                result = self.readline()
694            elif request.get('method') == 'ping':
695                result = request['args']
696            if result:
697                response = {'version': '1.1', 'id': request.get('id'),
698                        'result': result,
699                        'error': None}
700                self.send(response)
701            return True
702
703    def call(self, method, *args):
704        "Actually call the remote method (inside the thread)"
705        req = {'method': method, 'args': args, 'id': self.i}
706        self.send(req)
707        self.i += 1  # increment the id
708        while 1:
709            # wait until command acknowledge (response id match the request)
710            res = self.recv()
711            if 'id' not in res or not res['id']:
712                # nested notification received (i.e. write)! process it later...
713                self.notifies.append(res)
714            elif 'result' not in res:
715                # nested request received (i.e. readline)! process it!
716                self.process_message(res)
717            elif int(req['id']) != int(res['id']):
718                print("DEBUGGER wrong packet received: expecting id", req['id'], res['id'])
719                # protocol state is unknown
720            elif 'error' in res and res['error']:
721                raise RPCError(res['error']['message'])
722            else:
723                return res['result']
724
725    def do_step(self, arg=None):
726        "Execute the current line, stop at the first possible occasion"
727        self.call('do_step')
728       
729    def do_next(self, arg=None):
730        "Execute the current line, do not stop at function calls"
731        self.call('do_next')
732
733    def do_continue(self, arg=None):
734        "Continue execution, only stop when a breakpoint is encountered."
735        self.call('do_continue')
736       
737    def do_return(self, arg=None):
738        "Continue execution until the current function returns"
739        self.call('do_return')
740
741    def do_jump(self, arg):
742        "Set the next line that will be executed (None if sucess or message)"
743        res = self.call('do_jump', arg)
744        return res
745
746    def do_where(self, arg=None):
747        "Print a stack trace, with the most recent frame at the bottom."
748        return self.call('do_where')
749
750    def do_quit(self, arg=None):
751        "Quit from the debugger. The program being executed is aborted."
752        self.call('do_quit')
753   
754    def do_eval(self, expr):
755        "Inspect the value of the expression"
756        return self.call('do_eval', expr)
757
758    def do_environment(self):
759        "List all the locals and globals variables (string representation)"
760        return self.call('do_environment')
761
762    def do_list(self, arg=None):
763        "List source code for the current file"
764        return self.call('do_list', arg)
765
766    def do_read(self, filename):
767        "Read and send a local filename"
768        return self.call('do_read', filename)
769
770    def do_set_breakpoint(self, filename, lineno, temporary=0, cond=None):
771        "Set a breakpoint at filename:breakpoint"
772        self.call('do_set_breakpoint', filename, lineno, temporary, cond)
773
774    def do_clear_breakpoint(self, filename, lineno):
775        "Remove a breakpoint at filename:breakpoint"
776        self.call('do_clear_breakpoint', filename, lineno)
777
778    def do_clear_file_breakpoints(self, filename):
779        "Remove all breakpoints at filename"
780        self.call('do_clear_breakpoints', filename, lineno)
781       
782    def do_list_breakpoint(self):
783        "List all breakpoints"
784        return self.call('do_list_breakpoint')
785       
786    def do_exec(self, statement):
787        return self.call('do_exec', statement)
788
789    def get_autocomplete_list(self, expression):
790        return self.call('get_autocomplete_list', expression)
791
792    def get_call_tip(self, expression):
793        return self.call('get_call_tip', expression)
794       
795    def interrupt(self):
796        "Immediately stop at the first possible occasion (outside interaction)"
797        # this is a notification!, do not expect a response
798        req = {'method': 'interrupt', 'args': ()}
799        self.send(req)
800
801    def set_burst(self, value):
802        req = {'method': 'set_burst', 'args': (value, )}
803        self.send(req)
804       
805    def set_params(self, params):
806        req = {'method': 'set_params', 'args': (params, )}
807        self.send(req)
808
809
810class Cli(Frontend, cmd.Cmd):
811    "Qdb Front-end command line interface"
812   
813    def __init__(self, pipe, completekey='tab', stdin=None, stdout=None, skip=None):
814        cmd.Cmd.__init__(self, completekey, stdin, stdout)
815        Frontend.__init__(self, pipe)
816
817    # redefine Frontend methods:
818   
819    def run(self):
820        while 1:
821            try:
822                Frontend.run(self)
823            except KeyboardInterrupt:
824                print("Interupting...")
825                self.interrupt()
826
827    def interaction(self, filename, lineno, line):
828        print("> %s(%d)\n-> %s" % (filename, lineno, line), end=' ')
829        self.filename = filename
830        self.cmdloop()
831
832    def exception(self, title, extype, exvalue, trace, request):
833        print("=" * 80)
834        print("Exception", title)
835        print(request)
836        print("-" * 80)
837
838    def write(self, text):
839        print(text, end=' ')
840   
841    def readline(self):
842        return input()
843       
844    def postcmd(self, stop, line):
845        return not line.startswith("h") # stop
846
847    do_h = cmd.Cmd.do_help
848   
849    do_s = Frontend.do_step
850    do_n = Frontend.do_next
851    do_c = Frontend.do_continue       
852    do_r = Frontend.do_return
853    do_q = Frontend.do_quit
854
855    def do_eval(self, args):
856        "Inspect the value of the expression"
857        print(Frontend.do_eval(self, args))
858 
859    def do_list(self, args):
860        "List source code for the current file"
861        lines = Frontend.do_list(self, eval(args, {}, {}) if args else None)
862        self.print_lines(lines)
863   
864    def do_where(self, args):
865        "Print a stack trace, with the most recent frame at the bottom."
866        lines = Frontend.do_where(self)
867        self.print_lines(lines)
868
869    def do_environment(self, args=None):
870        env = Frontend.do_environment(self)
871        for key in env:
872            print("=" * 78)
873            print(key.capitalize())
874            print("-" * 78)
875            for name, value in list(env[key].items()):
876                print("%-12s = %s" % (name, value))
877
878    def do_list_breakpoint(self, arg=None):
879        "List all breakpoints"
880        breaks = Frontend.do_list_breakpoint(self)
881        print("Num File                          Line Temp Enab Hits Cond")
882        for bp in breaks:
883            print('%-4d%-30s%4d %4s %4s %4d %s' % bp)
884        print()
885
886    def do_set_breakpoint(self, arg):
887        "Set a breakpoint at filename:breakpoint"
888        if arg:
889            if ':' in arg:
890                args = arg.split(":")
891            else:
892                args = (self.filename, arg)
893            Frontend.do_set_breakpoint(self, *args)
894        else:
895            self.do_list_breakpoint()
896
897    def do_jump(self, args):
898        "Jump to the selected line"
899        ret = Frontend.do_jump(self, args)
900        if ret:     # show error message if failed
901            print("cannot jump:", ret)
902
903    do_b = do_set_breakpoint
904    do_l = do_list
905    do_p = do_eval
906    do_w = do_where
907    do_e = do_environment
908    do_j = do_jump
909
910    def default(self, line):
911        "Default command"
912        if line[:1] == '!':
913            print(self.do_exec(line[1:]))
914        else:
915            print("*** Unknown command: ", line)
916
917    def print_lines(self, lines):
918        for filename, lineno, bp, current, source in lines:
919            print("%s:%4d%s%s\t%s" % (filename, lineno, bp, current, source), end=' ')
920        print()
921
922
923# WORKAROUND for python3 server using pickle's HIGHEST_PROTOCOL (now 3)
924# but python2 client using pickles's protocol version 2
925if sys.version_info[0] > 2:
926
927    import multiprocessing.reduction        # forking in py2
928
929    class ForkingPickler2(multiprocessing.reduction.ForkingPickler):
930        def __init__(self, file, protocol=None, fix_imports=True):
931            # downgrade to protocol ver 2
932            protocol = 2
933            super().__init__(file, protocol, fix_imports)
934
935    multiprocessing.reduction.ForkingPickler = ForkingPickler2
936
937
938def f(pipe):
939    "test function to be debugged"
940    print("creating debugger")
941    qdb_test = Qdb(pipe=pipe, redirect_stdio=False, allow_interruptions=True)
942    print("set trace")
943
944    my_var = "Mariano!"
945    qdb_test.set_trace()
946    print("hello world!")
947    for i in range(100000):
948        pass
949    print("good by!")
950   
951
952def test():
953    "Create a backend/frontend and time it"
954    if '--process' in sys.argv:
955        from multiprocessing import Process, Pipe
956        front_conn, child_conn = Pipe()
957        p = Process(target=f, args=(child_conn,))
958    else:
959        from threading import Thread
960        from queue import Queue
961        parent_queue, child_queue = Queue(), Queue()
962        front_conn = QueuePipe("parent", parent_queue, child_queue)
963        child_conn = QueuePipe("child", child_queue, parent_queue)
964        p = Thread(target=f, args=(child_conn,))
965   
966    p.start()
967    import time
968
969    class Test(Frontend):
970        def interaction(self, *args):
971            print("interaction!", args)
972            ##self.do_next()
973        def exception(self, *args):
974            print("exception", args)
975
976    qdb_test = Test(front_conn)
977    time.sleep(1)
978    t0 = time.time()
979       
980    print("running...")
981    while front_conn.poll():
982        Frontend.run(qdb_test)
983    qdb_test.do_continue()
984    p.join()
985    t1 = time.time()
986    print("took", t1 - t0, "seconds")
987    sys.exit(0)
988
989
990def start(host="localhost", port=6000, authkey='secret password'):
991    "Start the CLI server and wait connection from a running debugger backend"
992   
993    address = (host, port)
994    from multiprocessing.connection import Listener
995    address = (host, port)     # family is deduced to be 'AF_INET'
996    if isinstance(authkey, str):
997        authkey = authkey.encode("utf8")
998    listener = Listener(address, authkey=authkey)
999    print("qdb debugger backend: waiting for connection at", address)
1000    conn = listener.accept()
1001    print('qdb debugger backend: connected to', listener.last_accepted)
1002    try:
1003        Cli(conn).run()
1004    except EOFError:
1005        pass
1006    finally:
1007        conn.close()
1008
1009
1010def main(host='localhost', port=6000, authkey='secret password'):
1011    "Debug a script (running under the backend) and connect to remote frontend"
1012   
1013    if not sys.argv[1:] or sys.argv[1] in ("--help", "-h"):
1014        print("usage: pdb.py scriptfile [arg] ...")
1015        sys.exit(2)
1016
1017    mainpyfile =  sys.argv[1]     # Get script filename
1018    if not os.path.exists(mainpyfile):
1019        print('Error:', mainpyfile, 'does not exist')
1020        sys.exit(1)
1021
1022    del sys.argv[0]         # Hide "pdb.py" from argument list
1023
1024    # Replace pdb's dir with script's dir in front of module search path.
1025    sys.path[0] = os.path.dirname(mainpyfile)
1026
1027    # create the backend
1028    init(host, port, authkey)
1029    try:
1030        print("running", mainpyfile)
1031        qdb._runscript(mainpyfile)
1032        print("The program finished")
1033    except SystemExit:
1034        # In most cases SystemExit does not warrant a post-mortem session.
1035        print("The program exited via sys.exit(). Exit status: ", end=' ')
1036        print(sys.exc_info()[1])
1037        raise
1038    except Exception:
1039        traceback.print_exc()
1040        print("Uncaught exception. Entering post mortem debugging")
1041        info = sys.exc_info()
1042        qdb.post_mortem(info)
1043        print("Program terminated!")
1044    finally:
1045        conn.close()
1046        print("qdb debbuger backend: connection closed")
1047
1048
1049# "singleton" to store a unique backend per process
1050qdb = None
1051
1052
1053def init(host='localhost', port=6000, authkey='secret password', redirect=True):
1054    "Simplified interface to debug running programs"
1055    global qdb, listener, conn
1056   
1057    # destroy the debugger if the previous connection is lost (i.e. broken pipe)
1058    if qdb and not qdb.ping():
1059        qdb.close()
1060        qdb = None
1061       
1062    from multiprocessing.connection import Client
1063    # only create it if not currently instantiated
1064    if not qdb:
1065        address = (host, port)     # family is deduced to be 'AF_INET'
1066        print("qdb debugger backend: waiting for connection to", address)
1067        if isinstance(authkey, str):
1068            authkey = authkey.encode("utf8")
1069        conn = Client(address, authkey=authkey)
1070        print('qdb debugger backend: connected to', address)
1071        # create the backend
1072        qdb = Qdb(conn, redirect_stdio=redirect, allow_interruptions=True)
1073        # initial hanshake
1074        qdb.startup()
1075
1076
1077def set_trace(host='localhost', port=6000, authkey='secret password'):
1078    "Simplified interface to start debugging immediately"
1079    init(host, port, authkey)
1080    # start debugger backend:
1081    qdb.set_trace()
1082
1083def debug(host='localhost', port=6000, authkey='secret password'):
1084    "Simplified interface to start debugging immediately (no stop)"
1085    init(host, port, authkey)
1086    # start debugger backend:
1087    qdb.do_debug()
1088   
1089
1090def quit():
1091    "Remove trace and quit"
1092    global qdb, listener, conn
1093    if qdb:
1094        sys.settrace(None)
1095        qdb = None
1096    if conn:
1097        conn.close()
1098        conn = None
1099    if listener:
1100        listener.close()
1101        listener = None
1102
1103if __name__ == '__main__':
1104    # When invoked as main program:
1105    if '--test1' in sys.argv:
1106        test()
1107    # Check environment for configuration parameters:
1108    kwargs = {}
1109    for param in 'host', 'port', 'authkey':
1110       if 'DBG_%s' % param.upper() in os.environ:
1111            kwargs[param] = os.environ['DBG_%s' % param.upper()]
1112
1113    if not sys.argv[1:]:
1114        # connect to a remote debbuger
1115        start(**kwargs)
1116    else:
1117        # start the debugger on a script
1118        # reimport as global __main__ namespace is destroyed
1119        import dbg
1120        dbg.main(**kwargs)
1121
Note: See TracBrowser for help on using the repository browser.