Update version number and release date.
[python/dscho.git] / Lib / idlelib / RemoteDebugger.py
blob41f910f45b7134c235de0bfc6051a801b2ad8329
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 idb_adap_oid = "idb_adapter"
31 gui_adap_oid = "gui_adapter"
33 #=======================================
35 # In the PYTHON subprocess:
37 frametable = {}
38 dicttable = {}
39 codetable = {}
40 tracebacktable = {}
42 def wrap_frame(frame):
43 fid = id(frame)
44 frametable[fid] = frame
45 return fid
47 def wrap_info(info):
48 "replace info[2], a traceback instance, by its ID"
49 if info is None:
50 return None
51 else:
52 traceback = info[2]
53 assert isinstance(traceback, types.TracebackType)
54 traceback_id = id(traceback)
55 tracebacktable[traceback_id] = traceback
56 modified_info = (info[0], info[1], traceback_id)
57 return modified_info
59 class GUIProxy:
61 def __init__(self, conn, gui_adap_oid):
62 self.conn = conn
63 self.oid = gui_adap_oid
65 def interaction(self, message, frame, info=None):
66 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
67 # pass frame and traceback object IDs instead of the objects themselves
68 self.conn.remotecall(self.oid, "interaction",
69 (message, wrap_frame(frame), wrap_info(info)),
70 {})
72 class IdbAdapter:
74 def __init__(self, idb):
75 self.idb = idb
77 #----------called by an IdbProxy----------
79 def set_step(self):
80 self.idb.set_step()
82 def set_quit(self):
83 self.idb.set_quit()
85 def set_continue(self):
86 self.idb.set_continue()
88 def set_next(self, fid):
89 frame = frametable[fid]
90 self.idb.set_next(frame)
92 def set_return(self, fid):
93 frame = frametable[fid]
94 self.idb.set_return(frame)
96 def get_stack(self, fid, tbid):
97 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
98 frame = frametable[fid]
99 if tbid is None:
100 tb = None
101 else:
102 tb = tracebacktable[tbid]
103 stack, i = self.idb.get_stack(frame, tb)
104 ##print >>sys.__stderr__, "get_stack() ->", stack
105 stack = [(wrap_frame(frame), k) for frame, k in stack]
106 ##print >>sys.__stderr__, "get_stack() ->", stack
107 return stack, i
109 def run(self, cmd):
110 import __main__
111 self.idb.run(cmd, __main__.__dict__)
113 def set_break(self, filename, lineno):
114 msg = self.idb.set_break(filename, lineno)
115 return msg
117 def clear_break(self, filename, lineno):
118 msg = self.idb.clear_break(filename, lineno)
119 return msg
121 def clear_all_file_breaks(self, filename):
122 msg = self.idb.clear_all_file_breaks(filename)
123 return msg
125 #----------called by a FrameProxy----------
127 def frame_attr(self, fid, name):
128 frame = frametable[fid]
129 return getattr(frame, name)
131 def frame_globals(self, fid):
132 frame = frametable[fid]
133 dict = frame.f_globals
134 did = id(dict)
135 dicttable[did] = dict
136 return did
138 def frame_locals(self, fid):
139 frame = frametable[fid]
140 dict = frame.f_locals
141 did = id(dict)
142 dicttable[did] = dict
143 return did
145 def frame_code(self, fid):
146 frame = frametable[fid]
147 code = frame.f_code
148 cid = id(code)
149 codetable[cid] = code
150 return cid
152 #----------called by a CodeProxy----------
154 def code_name(self, cid):
155 code = codetable[cid]
156 return code.co_name
158 def code_filename(self, cid):
159 code = codetable[cid]
160 return code.co_filename
162 #----------called by a DictProxy----------
164 def dict_keys(self, did):
165 dict = dicttable[did]
166 return dict.keys()
168 def dict_item(self, did, key):
169 dict = dicttable[did]
170 value = dict[key]
171 value = repr(value)
172 return value
174 #----------end class IdbAdapter----------
177 def start_debugger(rpchandler, gui_adap_oid):
178 """Start the debugger and its RPC link in the Python subprocess
180 Start the subprocess side of the split debugger and set up that side of the
181 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
182 objects and linking them together. Register the IdbAdapter with the
183 RPCServer to handle RPC requests from the split debugger GUI via the
184 IdbProxy.
187 gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
188 idb = Debugger.Idb(gui_proxy)
189 idb_adap = IdbAdapter(idb)
190 rpchandler.register(idb_adap_oid, idb_adap)
191 return idb_adap_oid
194 #=======================================
196 # In the IDLE process:
199 class FrameProxy:
201 def __init__(self, conn, fid):
202 self._conn = conn
203 self._fid = fid
204 self._oid = "idb_adapter"
205 self._dictcache = {}
207 def __getattr__(self, name):
208 if name[:1] == "_":
209 raise AttributeError, name
210 if name == "f_code":
211 return self._get_f_code()
212 if name == "f_globals":
213 return self._get_f_globals()
214 if name == "f_locals":
215 return self._get_f_locals()
216 return self._conn.remotecall(self._oid, "frame_attr",
217 (self._fid, name), {})
219 def _get_f_code(self):
220 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
221 return CodeProxy(self._conn, self._oid, cid)
223 def _get_f_globals(self):
224 did = self._conn.remotecall(self._oid, "frame_globals",
225 (self._fid,), {})
226 return self._get_dict_proxy(did)
228 def _get_f_locals(self):
229 did = self._conn.remotecall(self._oid, "frame_locals",
230 (self._fid,), {})
231 return self._get_dict_proxy(did)
233 def _get_dict_proxy(self, did):
234 if self._dictcache.has_key(did):
235 return self._dictcache[did]
236 dp = DictProxy(self._conn, self._oid, did)
237 self._dictcache[did] = dp
238 return dp
241 class CodeProxy:
243 def __init__(self, conn, oid, cid):
244 self._conn = conn
245 self._oid = oid
246 self._cid = cid
248 def __getattr__(self, name):
249 if name == "co_name":
250 return self._conn.remotecall(self._oid, "code_name",
251 (self._cid,), {})
252 if name == "co_filename":
253 return self._conn.remotecall(self._oid, "code_filename",
254 (self._cid,), {})
257 class DictProxy:
259 def __init__(self, conn, oid, did):
260 self._conn = conn
261 self._oid = oid
262 self._did = did
264 def keys(self):
265 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
267 def __getitem__(self, key):
268 return self._conn.remotecall(self._oid, "dict_item",
269 (self._did, key), {})
271 def __getattr__(self, name):
272 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
273 raise AttributeError, name
276 class GUIAdapter:
278 def __init__(self, conn, gui):
279 self.conn = conn
280 self.gui = gui
282 def interaction(self, message, fid, modified_info):
283 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
284 frame = FrameProxy(self.conn, fid)
285 self.gui.interaction(message, frame, modified_info)
288 class IdbProxy:
290 def __init__(self, conn, shell, oid):
291 self.oid = oid
292 self.conn = conn
293 self.shell = shell
295 def call(self, methodname, *args, **kwargs):
296 ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
297 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
298 ##print "**IdbProxy.call %s returns %s" % (methodname, `value`)
299 return value
301 def run(self, cmd, locals):
302 # Ignores locals on purpose!
303 seq = self.conn.asynccall(self.oid, "run", (cmd,), {})
304 self.shell.interp.active_seq = seq
306 def get_stack(self, frame, tbid):
307 # passing frame and traceback IDs, not the objects themselves
308 stack, i = self.call("get_stack", frame._fid, tbid)
309 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
310 return stack, i
312 def set_continue(self):
313 self.call("set_continue")
315 def set_step(self):
316 self.call("set_step")
318 def set_next(self, frame):
319 self.call("set_next", frame._fid)
321 def set_return(self, frame):
322 self.call("set_return", frame._fid)
324 def set_quit(self):
325 self.call("set_quit")
327 def set_break(self, filename, lineno):
328 msg = self.call("set_break", filename, lineno)
329 return msg
331 def clear_break(self, filename, lineno):
332 msg = self.call("clear_break", filename, lineno)
333 return msg
335 def clear_all_file_breaks(self, filename):
336 msg = self.call("clear_all_file_breaks", filename)
337 return msg
339 def start_remote_debugger(rpcclt, pyshell):
340 """Start the subprocess debugger, initialize the debugger GUI and RPC link
342 Request the RPCServer start the Python subprocess debugger and link. Set
343 up the Idle side of the split debugger by instantiating the IdbProxy,
344 debugger GUI, and debugger GUIAdapter objects and linking them together.
346 Register the GUIAdapter with the RPCClient to handle debugger GUI
347 interaction requests coming from the subprocess debugger via the GUIProxy.
349 The IdbAdapter will pass execution and environment requests coming from the
350 Idle debugger GUI to the subprocess debugger via the IdbProxy.
353 global idb_adap_oid
355 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
356 (gui_adap_oid,), {})
357 idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
358 gui = Debugger.Debugger(pyshell, idb_proxy)
359 gui_adap = GUIAdapter(rpcclt, gui)
360 rpcclt.register(gui_adap_oid, gui_adap)
361 return gui
363 def close_remote_debugger(rpcclt):
364 """Shut down subprocess debugger and Idle side of debugger RPC link
366 Request that the RPCServer shut down the subprocess debugger and link.
367 Unregister the GUIAdapter, which will cause a GC on the Idle process
368 debugger and RPC link objects. (The second reference to the debugger GUI
369 is deleted in PyShell.close_remote_debugger().)
372 close_subprocess_debugger(rpcclt)
373 rpcclt.unregister(gui_adap_oid)
375 def close_subprocess_debugger(rpcclt):
376 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
378 def restart_subprocess_debugger(rpcclt):
379 idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
380 (gui_adap_oid,), {})
381 assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'