diff options
Diffstat (limited to 'Lib/idlelib/debugger_r.py')
| -rw-r--r-- | Lib/idlelib/debugger_r.py | 388 | 
1 files changed, 388 insertions, 0 deletions
| diff --git a/Lib/idlelib/debugger_r.py b/Lib/idlelib/debugger_r.py new file mode 100644 index 0000000..bc97127 --- /dev/null +++ b/Lib/idlelib/debugger_r.py @@ -0,0 +1,388 @@ +"""Support for remote Python debugging. + +Some ASCII art to describe the structure: + +       IN PYTHON SUBPROCESS          #             IN IDLE PROCESS +                                     # +                                     #        oid='gui_adapter' +                 +----------+        #       +------------+          +-----+ +                 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI | ++-----+--calls-->+----------+        #       +------------+          +-----+ +| Idb |                               #                             / ++-----+<-calls--+------------+         #      +----------+<--calls-/ +                | IdbAdapter |<--remote#call--| IdbProxy | +                +------------+         #      +----------+ +                oid='idb_adapter'      # + +The purpose of the Proxy and Adapter classes is to translate certain +arguments and return values that cannot be transported through the RPC +barrier, in particular frame and traceback objects. + +""" + +import types +from idlelib import debugger + +debugging = 0 + +idb_adap_oid = "idb_adapter" +gui_adap_oid = "gui_adapter" + +#======================================= +# +# In the PYTHON subprocess: + +frametable = {} +dicttable = {} +codetable = {} +tracebacktable = {} + +def wrap_frame(frame): +    fid = id(frame) +    frametable[fid] = frame +    return fid + +def wrap_info(info): +    "replace info[2], a traceback instance, by its ID" +    if info is None: +        return None +    else: +        traceback = info[2] +        assert isinstance(traceback, types.TracebackType) +        traceback_id = id(traceback) +        tracebacktable[traceback_id] = traceback +        modified_info = (info[0], info[1], traceback_id) +        return modified_info + +class GUIProxy: + +    def __init__(self, conn, gui_adap_oid): +        self.conn = conn +        self.oid = gui_adap_oid + +    def interaction(self, message, frame, info=None): +        # calls rpc.SocketIO.remotecall() via run.MyHandler instance +        # pass frame and traceback object IDs instead of the objects themselves +        self.conn.remotecall(self.oid, "interaction", +                             (message, wrap_frame(frame), wrap_info(info)), +                             {}) + +class IdbAdapter: + +    def __init__(self, idb): +        self.idb = idb + +    #----------called by an IdbProxy---------- + +    def set_step(self): +        self.idb.set_step() + +    def set_quit(self): +        self.idb.set_quit() + +    def set_continue(self): +        self.idb.set_continue() + +    def set_next(self, fid): +        frame = frametable[fid] +        self.idb.set_next(frame) + +    def set_return(self, fid): +        frame = frametable[fid] +        self.idb.set_return(frame) + +    def get_stack(self, fid, tbid): +        frame = frametable[fid] +        if tbid is None: +            tb = None +        else: +            tb = tracebacktable[tbid] +        stack, i = self.idb.get_stack(frame, tb) +        stack = [(wrap_frame(frame2), k) for frame2, k in stack] +        return stack, i + +    def run(self, cmd): +        import __main__ +        self.idb.run(cmd, __main__.__dict__) + +    def set_break(self, filename, lineno): +        msg = self.idb.set_break(filename, lineno) +        return msg + +    def clear_break(self, filename, lineno): +        msg = self.idb.clear_break(filename, lineno) +        return msg + +    def clear_all_file_breaks(self, filename): +        msg = self.idb.clear_all_file_breaks(filename) +        return msg + +    #----------called by a FrameProxy---------- + +    def frame_attr(self, fid, name): +        frame = frametable[fid] +        return getattr(frame, name) + +    def frame_globals(self, fid): +        frame = frametable[fid] +        dict = frame.f_globals +        did = id(dict) +        dicttable[did] = dict +        return did + +    def frame_locals(self, fid): +        frame = frametable[fid] +        dict = frame.f_locals +        did = id(dict) +        dicttable[did] = dict +        return did + +    def frame_code(self, fid): +        frame = frametable[fid] +        code = frame.f_code +        cid = id(code) +        codetable[cid] = code +        return cid + +    #----------called by a CodeProxy---------- + +    def code_name(self, cid): +        code = codetable[cid] +        return code.co_name + +    def code_filename(self, cid): +        code = codetable[cid] +        return code.co_filename + +    #----------called by a DictProxy---------- + +    def dict_keys(self, did): +        raise NotImplemented("dict_keys not public or pickleable") +##         dict = dicttable[did] +##         return dict.keys() + +    ### Needed until dict_keys is type is finished and pickealable. +    ### Will probably need to extend rpc.py:SocketIO._proxify at that time. +    def dict_keys_list(self, did): +        dict = dicttable[did] +        return list(dict.keys()) + +    def dict_item(self, did, key): +        dict = dicttable[did] +        value = dict[key] +        value = repr(value) ### can't pickle module 'builtins' +        return value + +#----------end class IdbAdapter---------- + + +def start_debugger(rpchandler, gui_adap_oid): +    """Start the debugger and its RPC link in the Python subprocess + +    Start the subprocess side of the split debugger and set up that side of the +    RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter +    objects and linking them together.  Register the IdbAdapter with the +    RPCServer to handle RPC requests from the split debugger GUI via the +    IdbProxy. + +    """ +    gui_proxy = GUIProxy(rpchandler, gui_adap_oid) +    idb = debugger.Idb(gui_proxy) +    idb_adap = IdbAdapter(idb) +    rpchandler.register(idb_adap_oid, idb_adap) +    return idb_adap_oid + + +#======================================= +# +# In the IDLE process: + + +class FrameProxy: + +    def __init__(self, conn, fid): +        self._conn = conn +        self._fid = fid +        self._oid = "idb_adapter" +        self._dictcache = {} + +    def __getattr__(self, name): +        if name[:1] == "_": +            raise AttributeError(name) +        if name == "f_code": +            return self._get_f_code() +        if name == "f_globals": +            return self._get_f_globals() +        if name == "f_locals": +            return self._get_f_locals() +        return self._conn.remotecall(self._oid, "frame_attr", +                                     (self._fid, name), {}) + +    def _get_f_code(self): +        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {}) +        return CodeProxy(self._conn, self._oid, cid) + +    def _get_f_globals(self): +        did = self._conn.remotecall(self._oid, "frame_globals", +                                    (self._fid,), {}) +        return self._get_dict_proxy(did) + +    def _get_f_locals(self): +        did = self._conn.remotecall(self._oid, "frame_locals", +                                    (self._fid,), {}) +        return self._get_dict_proxy(did) + +    def _get_dict_proxy(self, did): +        if did in self._dictcache: +            return self._dictcache[did] +        dp = DictProxy(self._conn, self._oid, did) +        self._dictcache[did] = dp +        return dp + + +class CodeProxy: + +    def __init__(self, conn, oid, cid): +        self._conn = conn +        self._oid = oid +        self._cid = cid + +    def __getattr__(self, name): +        if name == "co_name": +            return self._conn.remotecall(self._oid, "code_name", +                                         (self._cid,), {}) +        if name == "co_filename": +            return self._conn.remotecall(self._oid, "code_filename", +                                         (self._cid,), {}) + + +class DictProxy: + +    def __init__(self, conn, oid, did): +        self._conn = conn +        self._oid = oid +        self._did = did + +##    def keys(self): +##        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {}) + +    # 'temporary' until dict_keys is a pickleable built-in type +    def keys(self): +        return self._conn.remotecall(self._oid, +                                     "dict_keys_list", (self._did,), {}) + +    def __getitem__(self, key): +        return self._conn.remotecall(self._oid, "dict_item", +                                     (self._did, key), {}) + +    def __getattr__(self, name): +        ##print("*** Failed DictProxy.__getattr__:", name) +        raise AttributeError(name) + + +class GUIAdapter: + +    def __init__(self, conn, gui): +        self.conn = conn +        self.gui = gui + +    def interaction(self, message, fid, modified_info): +        ##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info)) +        frame = FrameProxy(self.conn, fid) +        self.gui.interaction(message, frame, modified_info) + + +class IdbProxy: + +    def __init__(self, conn, shell, oid): +        self.oid = oid +        self.conn = conn +        self.shell = shell + +    def call(self, methodname, *args, **kwargs): +        ##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs)) +        value = self.conn.remotecall(self.oid, methodname, args, kwargs) +        ##print("*** IdbProxy.call %s returns %r" % (methodname, value)) +        return value + +    def run(self, cmd, locals): +        # Ignores locals on purpose! +        seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {}) +        self.shell.interp.active_seq = seq + +    def get_stack(self, frame, tbid): +        # passing frame and traceback IDs, not the objects themselves +        stack, i = self.call("get_stack", frame._fid, tbid) +        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack] +        return stack, i + +    def set_continue(self): +        self.call("set_continue") + +    def set_step(self): +        self.call("set_step") + +    def set_next(self, frame): +        self.call("set_next", frame._fid) + +    def set_return(self, frame): +        self.call("set_return", frame._fid) + +    def set_quit(self): +        self.call("set_quit") + +    def set_break(self, filename, lineno): +        msg = self.call("set_break", filename, lineno) +        return msg + +    def clear_break(self, filename, lineno): +        msg = self.call("clear_break", filename, lineno) +        return msg + +    def clear_all_file_breaks(self, filename): +        msg = self.call("clear_all_file_breaks", filename) +        return msg + +def start_remote_debugger(rpcclt, pyshell): +    """Start the subprocess debugger, initialize the debugger GUI and RPC link + +    Request the RPCServer start the Python subprocess debugger and link.  Set +    up the Idle side of the split debugger by instantiating the IdbProxy, +    debugger GUI, and debugger GUIAdapter objects and linking them together. + +    Register the GUIAdapter with the RPCClient to handle debugger GUI +    interaction requests coming from the subprocess debugger via the GUIProxy. + +    The IdbAdapter will pass execution and environment requests coming from the +    Idle debugger GUI to the subprocess debugger via the IdbProxy. + +    """ +    global idb_adap_oid + +    idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ +                                   (gui_adap_oid,), {}) +    idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid) +    gui = debugger.Debugger(pyshell, idb_proxy) +    gui_adap = GUIAdapter(rpcclt, gui) +    rpcclt.register(gui_adap_oid, gui_adap) +    return gui + +def close_remote_debugger(rpcclt): +    """Shut down subprocess debugger and Idle side of debugger RPC link + +    Request that the RPCServer shut down the subprocess debugger and link. +    Unregister the GUIAdapter, which will cause a GC on the Idle process +    debugger and RPC link objects.  (The second reference to the debugger GUI +    is deleted in pyshell.close_remote_debugger().) + +    """ +    close_subprocess_debugger(rpcclt) +    rpcclt.unregister(gui_adap_oid) + +def close_subprocess_debugger(rpcclt): +    rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) + +def restart_subprocess_debugger(rpcclt): +    idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ +                                         (gui_adap_oid,), {}) +    assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' | 
