Use full package paths in imports.
[python/dscho.git] / Lib / idlelib / RemoteDebugger.py
blob84e94df607a225ee8fa8429e884f0d5f709b6eca
1 """Support for remote Python debugging.
3 Some ASCII art to describe the structure:
5 IN PYTHON SUBPROCESS # IN IDLE PROCESS
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' #
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.
21 """
23 import sys
24 import types
25 import rpc
26 import Debugger
28 debugging = 0
30 #=======================================
32 # In the PYTHON subprocess:
34 frametable = {}
35 dicttable = {}
36 codetable = {}
37 tracebacktable = {}
39 def wrap_frame(frame):
40 fid = id(frame)
41 frametable[fid] = frame
42 return fid
44 def wrap_info(info):
45 "replace info[2], a traceback instance, by its ID"
46 if info is None:
47 return None
48 else:
49 traceback = info[2]
50 assert isinstance(traceback, types.TracebackType)
51 traceback_id = id(traceback)
52 tracebacktable[traceback_id] = traceback
53 modified_info = (info[0], info[1], traceback_id)
54 return modified_info
56 class GUIProxy:
58 def __init__(self, conn, gui_adap_oid):
59 self.conn = conn
60 self.oid = gui_adap_oid
62 def interaction(self, message, frame, info=None):
63 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
64 # pass frame and traceback object IDs instead of the objects themselves
65 self.conn.remotecall(self.oid, "interaction",
66 (message, wrap_frame(frame), wrap_info(info)),
67 {})
69 class IdbAdapter:
71 def __init__(self, idb):
72 self.idb = idb
74 #----------called by an IdbProxy----------
76 def set_step(self):
77 self.idb.set_step()
79 def set_quit(self):
80 self.idb.set_quit()
82 def set_continue(self):
83 self.idb.set_continue()
85 def set_next(self, fid):
86 frame = frametable[fid]
87 self.idb.set_next(frame)
89 def set_return(self, fid):
90 frame = frametable[fid]
91 self.idb.set_return(frame)
93 def get_stack(self, fid, tbid):
94 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`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 ##print >>sys.__stderr__, "get_stack() ->", stack
102 stack = [(wrap_frame(frame), k) for frame, k in stack]
103 ##print >>sys.__stderr__, "get_stack() ->", stack
104 return stack, i
106 def run(self, cmd):
107 import __main__
108 self.idb.run(cmd, __main__.__dict__)
110 def set_break(self, filename, lineno):
111 msg = self.idb.set_break(filename, lineno)
112 return msg
114 def clear_break(self, filename, lineno):
115 msg = self.idb.clear_break(filename, lineno)
117 def clear_all_file_breaks(self, filename):
118 msg = self.idb.clear_all_file_breaks(filename)
119 return msg
121 #----------called by a FrameProxy----------
123 def frame_attr(self, fid, name):
124 frame = frametable[fid]
125 return getattr(frame, name)
127 def frame_globals(self, fid):
128 frame = frametable[fid]
129 dict = frame.f_globals
130 did = id(dict)
131 dicttable[did] = dict
132 return did
134 def frame_locals(self, fid):
135 frame = frametable[fid]
136 dict = frame.f_locals
137 did = id(dict)
138 dicttable[did] = dict
139 return did
141 def frame_code(self, fid):
142 frame = frametable[fid]
143 code = frame.f_code
144 cid = id(code)
145 codetable[cid] = code
146 return cid
148 #----------called by a CodeProxy----------
150 def code_name(self, cid):
151 code = codetable[cid]
152 return code.co_name
154 def code_filename(self, cid):
155 code = codetable[cid]
156 return code.co_filename
158 #----------called by a DictProxy----------
160 def dict_keys(self, did):
161 dict = dicttable[did]
162 return dict.keys()
164 def dict_item(self, did, key):
165 dict = dicttable[did]
166 value = dict[key]
167 value = repr(value)
168 return value
170 #----------end class IdbAdapter----------
173 def start_debugger(rpchandler, gui_adap_oid):
174 """Start the debugger and its RPC link in the Python subprocess
176 Start the subprocess side of the split debugger and set up that side of the
177 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
178 objects and linking them together. Register the IdbAdapter with the
179 RPCServer to handle RPC requests from the split debugger GUI via the
180 IdbProxy.
183 gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
184 idb = Debugger.Idb(gui_proxy)
185 idb_adap = IdbAdapter(idb)
186 idb_adap_oid = "idb_adapter"
187 rpchandler.register(idb_adap_oid, idb_adap)
188 return idb_adap_oid
191 #=======================================
193 # In the IDLE process:
196 class FrameProxy:
198 def __init__(self, conn, fid):
199 self._conn = conn
200 self._fid = fid
201 self._oid = "idb_adapter"
202 self._dictcache = {}
204 def __getattr__(self, name):
205 if name[:1] == "_":
206 raise AttributeError, name
207 if name == "f_code":
208 return self._get_f_code()
209 if name == "f_globals":
210 return self._get_f_globals()
211 if name == "f_locals":
212 return self._get_f_locals()
213 return self._conn.remotecall(self._oid, "frame_attr",
214 (self._fid, name), {})
216 def _get_f_code(self):
217 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
218 return CodeProxy(self._conn, self._oid, cid)
220 def _get_f_globals(self):
221 did = self._conn.remotecall(self._oid, "frame_globals",
222 (self._fid,), {})
223 return self._get_dict_proxy(did)
225 def _get_f_locals(self):
226 did = self._conn.remotecall(self._oid, "frame_locals",
227 (self._fid,), {})
228 return self._get_dict_proxy(did)
230 def _get_dict_proxy(self, did):
231 if self._dictcache.has_key(did):
232 return self._dictcache[did]
233 dp = DictProxy(self._conn, self._oid, did)
234 self._dictcache[did] = dp
235 return dp
238 class CodeProxy:
240 def __init__(self, conn, oid, cid):
241 self._conn = conn
242 self._oid = oid
243 self._cid = cid
245 def __getattr__(self, name):
246 if name == "co_name":
247 return self._conn.remotecall(self._oid, "code_name",
248 (self._cid,), {})
249 if name == "co_filename":
250 return self._conn.remotecall(self._oid, "code_filename",
251 (self._cid,), {})
254 class DictProxy:
256 def __init__(self, conn, oid, did):
257 self._conn = conn
258 self._oid = oid
259 self._did = did
261 def keys(self):
262 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
264 def __getitem__(self, key):
265 return self._conn.remotecall(self._oid, "dict_item",
266 (self._did, key), {})
268 def __getattr__(self, name):
269 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
270 raise AttributeError, name
273 class GUIAdapter:
275 def __init__(self, conn, gui):
276 self.conn = conn
277 self.gui = gui
279 def interaction(self, message, fid, modified_info):
280 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
281 frame = FrameProxy(self.conn, fid)
282 self.gui.interaction(message, frame, modified_info)
285 class IdbProxy:
287 def __init__(self, conn, oid):
288 self.oid = oid
289 self.conn = conn
291 def call(self, methodname, *args, **kwargs):
292 ##print "call %s %s %s" % (methodname, args, kwargs)
293 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
294 ##print "return %s" % `value`
295 return value
297 def run(self, cmd, locals):
298 # Ignores locals on purpose!
299 self.call("run", cmd)
301 def get_stack(self, frame, tbid):
302 # passing frame and traceback IDs, not the objects themselves
303 stack, i = self.call("get_stack", frame._fid, tbid)
304 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
305 return stack, i
307 def set_continue(self):
308 self.call("set_continue")
310 def set_step(self):
311 self.call("set_step")
313 def set_next(self, frame):
314 self.call("set_next", frame._fid)
316 def set_return(self, frame):
317 self.call("set_return", frame._fid)
319 def set_quit(self):
320 self.call("set_quit")
322 def set_break(self, filename, lineno):
323 msg = self.call("set_break", filename, lineno)
324 return msg
326 def clear_break(self, filename, lineno):
327 msg = self.call("clear_break", filename, lineno)
329 def clear_all_file_breaks(self, filename):
330 msg = self.call("clear_all_file_breaks", filename)
331 return msg
333 def start_remote_debugger(rpcclt, pyshell):
334 """Start the subprocess debugger, initialize the debugger GUI and RPC link
336 Request the RPCServer start the Python subprocess debugger and link. Set
337 up the Idle side of the split debugger by instantiating the IdbProxy,
338 debugger GUI, and debugger GUIAdapter objects and linking them together.
340 Register the GUIAdapter with the RPCClient to handle debugger GUI
341 interaction requests coming from the subprocess debugger via the GUIProxy.
343 The IdbAdapter will pass execution and environment requests coming from the
344 Idle debugger GUI to the subprocess debugger via the IdbProxy.
347 gui_adap_oid = "gui_adapter"
348 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
349 (gui_adap_oid,), {})
350 idb_proxy = IdbProxy(rpcclt, idb_adap_oid)
351 gui = Debugger.Debugger(pyshell, idb_proxy)
352 gui_adap = GUIAdapter(rpcclt, gui)
353 rpcclt.register(gui_adap_oid, gui_adap)
354 return gui
356 def close_remote_debugger(rpcclt):
357 """Shut down subprocess debugger and Idle side of debugger RPC link
359 Request that the RPCServer shut down the subprocess debugger and link.
360 Unregister the GUIAdapter, which will cause a GC on the Idle process
361 debugger and RPC link objects. (The second reference to the debugger GUI
362 is deleted in PyShell.close_remote_debugger().)
364 """
365 idb_adap_oid = "idb_adapter"
366 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
367 gui_adap_oid = "gui_adapter"
368 rpcclt.unregister(gui_adap_oid)