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