Home | History | Annotate | Download | only in idlelib
      1 """Support for remote Python debugging.
      2 
      3 Some ASCII art to describe the structure:
      4 
      5        IN PYTHON SUBPROCESS          #             IN IDLE PROCESS
      6                                      #
      7                                      #        oid='gui_adapter'
      8                  +----------+        #       +------------+          +-----+
      9                  | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
     10 +-----+--calls-->+----------+        #       +------------+          +-----+
     11 | Idb |                               #                             /
     12 +-----+<-calls--+------------+         #      +----------+<--calls-/
     13                 | IdbAdapter |<--remote#call--| IdbProxy |
     14                 +------------+         #      +----------+
     15                 oid='idb_adapter'      #
     16 
     17 The purpose of the Proxy and Adapter classes is to translate certain
     18 arguments and return values that cannot be transported through the RPC
     19 barrier, in particular frame and traceback objects.
     20 
     21 """
     22 
     23 import types
     24 from idlelib import rpc
     25 from idlelib import Debugger
     26 
     27 debugging = 0
     28 
     29 idb_adap_oid = "idb_adapter"
     30 gui_adap_oid = "gui_adapter"
     31 
     32 #=======================================
     33 #
     34 # In the PYTHON subprocess:
     35 
     36 frametable = {}
     37 dicttable = {}
     38 codetable = {}
     39 tracebacktable = {}
     40 
     41 def wrap_frame(frame):
     42     fid = id(frame)
     43     frametable[fid] = frame
     44     return fid
     45 
     46 def wrap_info(info):
     47     "replace info[2], a traceback instance, by its ID"
     48     if info is None:
     49         return None
     50     else:
     51         traceback = info[2]
     52         assert isinstance(traceback, types.TracebackType)
     53         traceback_id = id(traceback)
     54         tracebacktable[traceback_id] = traceback
     55         modified_info = (info[0], info[1], traceback_id)
     56         return modified_info
     57 
     58 class GUIProxy:
     59 
     60     def __init__(self, conn, gui_adap_oid):
     61         self.conn = conn
     62         self.oid = gui_adap_oid
     63 
     64     def interaction(self, message, frame, info=None):
     65         # calls rpc.SocketIO.remotecall() via run.MyHandler instance
     66         # pass frame and traceback object IDs instead of the objects themselves
     67         self.conn.remotecall(self.oid, "interaction",
     68                              (message, wrap_frame(frame), wrap_info(info)),
     69                              {})
     70 
     71 class IdbAdapter:
     72 
     73     def __init__(self, idb):
     74         self.idb = idb
     75 
     76     #----------called by an IdbProxy----------
     77 
     78     def set_step(self):
     79         self.idb.set_step()
     80 
     81     def set_quit(self):
     82         self.idb.set_quit()
     83 
     84     def set_continue(self):
     85         self.idb.set_continue()
     86 
     87     def set_next(self, fid):
     88         frame = frametable[fid]
     89         self.idb.set_next(frame)
     90 
     91     def set_return(self, fid):
     92         frame = frametable[fid]
     93         self.idb.set_return(frame)
     94 
     95     def get_stack(self, fid, tbid):
     96         ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid)
     97         frame = frametable[fid]
     98         if tbid is None:
     99             tb = None
    100         else:
    101             tb = tracebacktable[tbid]
    102         stack, i = self.idb.get_stack(frame, tb)
    103         ##print >>sys.__stderr__, "get_stack() ->", stack
    104         stack = [(wrap_frame(frame), k) for frame, k in stack]
    105         ##print >>sys.__stderr__, "get_stack() ->", stack
    106         return stack, i
    107 
    108     def run(self, cmd):
    109         import __main__
    110         self.idb.run(cmd, __main__.__dict__)
    111 
    112     def set_break(self, filename, lineno):
    113         msg = self.idb.set_break(filename, lineno)
    114         return msg
    115 
    116     def clear_break(self, filename, lineno):
    117         msg = self.idb.clear_break(filename, lineno)
    118         return msg
    119 
    120     def clear_all_file_breaks(self, filename):
    121         msg = self.idb.clear_all_file_breaks(filename)
    122         return msg
    123 
    124     #----------called by a FrameProxy----------
    125 
    126     def frame_attr(self, fid, name):
    127         frame = frametable[fid]
    128         return getattr(frame, name)
    129 
    130     def frame_globals(self, fid):
    131         frame = frametable[fid]
    132         dict = frame.f_globals
    133         did = id(dict)
    134         dicttable[did] = dict
    135         return did
    136 
    137     def frame_locals(self, fid):
    138         frame = frametable[fid]
    139         dict = frame.f_locals
    140         did = id(dict)
    141         dicttable[did] = dict
    142         return did
    143 
    144     def frame_code(self, fid):
    145         frame = frametable[fid]
    146         code = frame.f_code
    147         cid = id(code)
    148         codetable[cid] = code
    149         return cid
    150 
    151     #----------called by a CodeProxy----------
    152 
    153     def code_name(self, cid):
    154         code = codetable[cid]
    155         return code.co_name
    156 
    157     def code_filename(self, cid):
    158         code = codetable[cid]
    159         return code.co_filename
    160 
    161     #----------called by a DictProxy----------
    162 
    163     def dict_keys(self, did):
    164         dict = dicttable[did]
    165         return dict.keys()
    166 
    167     def dict_item(self, did, key):
    168         dict = dicttable[did]
    169         value = dict[key]
    170         value = repr(value)
    171         return value
    172 
    173 #----------end class IdbAdapter----------
    174 
    175 
    176 def start_debugger(rpchandler, gui_adap_oid):
    177     """Start the debugger and its RPC link in the Python subprocess
    178 
    179     Start the subprocess side of the split debugger and set up that side of the
    180     RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
    181     objects and linking them together.  Register the IdbAdapter with the
    182     RPCServer to handle RPC requests from the split debugger GUI via the
    183     IdbProxy.
    184 
    185     """
    186     gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
    187     idb = Debugger.Idb(gui_proxy)
    188     idb_adap = IdbAdapter(idb)
    189     rpchandler.register(idb_adap_oid, idb_adap)
    190     return idb_adap_oid
    191 
    192 
    193 #=======================================
    194 #
    195 # In the IDLE process:
    196 
    197 
    198 class FrameProxy:
    199 
    200     def __init__(self, conn, fid):
    201         self._conn = conn
    202         self._fid = fid
    203         self._oid = "idb_adapter"
    204         self._dictcache = {}
    205 
    206     def __getattr__(self, name):
    207         if name[:1] == "_":
    208             raise AttributeError, name
    209         if name == "f_code":
    210             return self._get_f_code()
    211         if name == "f_globals":
    212             return self._get_f_globals()
    213         if name == "f_locals":
    214             return self._get_f_locals()
    215         return self._conn.remotecall(self._oid, "frame_attr",
    216                                      (self._fid, name), {})
    217 
    218     def _get_f_code(self):
    219         cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
    220         return CodeProxy(self._conn, self._oid, cid)
    221 
    222     def _get_f_globals(self):
    223         did = self._conn.remotecall(self._oid, "frame_globals",
    224                                     (self._fid,), {})
    225         return self._get_dict_proxy(did)
    226 
    227     def _get_f_locals(self):
    228         did = self._conn.remotecall(self._oid, "frame_locals",
    229                                     (self._fid,), {})
    230         return self._get_dict_proxy(did)
    231 
    232     def _get_dict_proxy(self, did):
    233         if did in self._dictcache:
    234             return self._dictcache[did]
    235         dp = DictProxy(self._conn, self._oid, did)
    236         self._dictcache[did] = dp
    237         return dp
    238 
    239 
    240 class CodeProxy:
    241 
    242     def __init__(self, conn, oid, cid):
    243         self._conn = conn
    244         self._oid = oid
    245         self._cid = cid
    246 
    247     def __getattr__(self, name):
    248         if name == "co_name":
    249             return self._conn.remotecall(self._oid, "code_name",
    250                                          (self._cid,), {})
    251         if name == "co_filename":
    252             return self._conn.remotecall(self._oid, "code_filename",
    253                                          (self._cid,), {})
    254 
    255 
    256 class DictProxy:
    257 
    258     def __init__(self, conn, oid, did):
    259         self._conn = conn
    260         self._oid = oid
    261         self._did = did
    262 
    263     def keys(self):
    264         return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
    265 
    266     def __getitem__(self, key):
    267         return self._conn.remotecall(self._oid, "dict_item",
    268                                      (self._did, key), {})
    269 
    270     def __getattr__(self, name):
    271         ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
    272         raise AttributeError, name
    273 
    274 
    275 class GUIAdapter:
    276 
    277     def __init__(self, conn, gui):
    278         self.conn = conn
    279         self.gui = gui
    280 
    281     def interaction(self, message, fid, modified_info):
    282         ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
    283         frame = FrameProxy(self.conn, fid)
    284         self.gui.interaction(message, frame, modified_info)
    285 
    286 
    287 class IdbProxy:
    288 
    289     def __init__(self, conn, shell, oid):
    290         self.oid = oid
    291         self.conn = conn
    292         self.shell = shell
    293 
    294     def call(self, methodname, *args, **kwargs):
    295         ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
    296         value = self.conn.remotecall(self.oid, methodname, args, kwargs)
    297         ##print "**IdbProxy.call %s returns %r" % (methodname, value)
    298         return value
    299 
    300     def run(self, cmd, locals):
    301         # Ignores locals on purpose!
    302         seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
    303         self.shell.interp.active_seq = seq
    304 
    305     def get_stack(self, frame, tbid):
    306         # passing frame and traceback IDs, not the objects themselves
    307         stack, i = self.call("get_stack", frame._fid, tbid)
    308         stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
    309         return stack, i
    310 
    311     def set_continue(self):
    312         self.call("set_continue")
    313 
    314     def set_step(self):
    315         self.call("set_step")
    316 
    317     def set_next(self, frame):
    318         self.call("set_next", frame._fid)
    319 
    320     def set_return(self, frame):
    321         self.call("set_return", frame._fid)
    322 
    323     def set_quit(self):
    324         self.call("set_quit")
    325 
    326     def set_break(self, filename, lineno):
    327         msg = self.call("set_break", filename, lineno)
    328         return msg
    329 
    330     def clear_break(self, filename, lineno):
    331         msg = self.call("clear_break", filename, lineno)
    332         return msg
    333 
    334     def clear_all_file_breaks(self, filename):
    335         msg = self.call("clear_all_file_breaks", filename)
    336         return msg
    337 
    338 def start_remote_debugger(rpcclt, pyshell):
    339     """Start the subprocess debugger, initialize the debugger GUI and RPC link
    340 
    341     Request the RPCServer start the Python subprocess debugger and link.  Set
    342     up the Idle side of the split debugger by instantiating the IdbProxy,
    343     debugger GUI, and debugger GUIAdapter objects and linking them together.
    344 
    345     Register the GUIAdapter with the RPCClient to handle debugger GUI
    346     interaction requests coming from the subprocess debugger via the GUIProxy.
    347 
    348     The IdbAdapter will pass execution and environment requests coming from the
    349     Idle debugger GUI to the subprocess debugger via the IdbProxy.
    350 
    351     """
    352     global idb_adap_oid
    353 
    354     idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
    355                                    (gui_adap_oid,), {})
    356     idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
    357     gui = Debugger.Debugger(pyshell, idb_proxy)
    358     gui_adap = GUIAdapter(rpcclt, gui)
    359     rpcclt.register(gui_adap_oid, gui_adap)
    360     return gui
    361 
    362 def close_remote_debugger(rpcclt):
    363     """Shut down subprocess debugger and Idle side of debugger RPC link
    364 
    365     Request that the RPCServer shut down the subprocess debugger and link.
    366     Unregister the GUIAdapter, which will cause a GC on the Idle process
    367     debugger and RPC link objects.  (The second reference to the debugger GUI
    368     is deleted in PyShell.close_remote_debugger().)
    369 
    370     """
    371     close_subprocess_debugger(rpcclt)
    372     rpcclt.unregister(gui_adap_oid)
    373 
    374 def close_subprocess_debugger(rpcclt):
    375     rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
    376 
    377 def restart_subprocess_debugger(rpcclt):
    378     idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
    379                                          (gui_adap_oid,), {})
    380     assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
    381