blob: 74085c36f51e7137d5221da10c205f3d66d52361 [file] [log] [blame]
Chui Tey5d2af632002-05-26 13:36:41 +00001"""Support for remote Python debugging.
2
3Some 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
17The purpose of the Proxy and Adapter classes is to translate certain
18arguments and return values that cannot be transported through the RPC
19barrier, in particular frame and traceback objects.
20
21"""
22
23import sys
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000024import types
Chui Tey5d2af632002-05-26 13:36:41 +000025import rpc
26import Debugger
27
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000028debugging = 0
29
Kurt B. Kaiser63857a42002-09-05 02:31:20 +000030idb_adap_oid = "idb_adapter"
31gui_adap_oid = "gui_adapter"
32
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +000033#=======================================
34#
35# In the PYTHON subprocess:
Chui Tey5d2af632002-05-26 13:36:41 +000036
37frametable = {}
38dicttable = {}
39codetable = {}
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000040tracebacktable = {}
Chui Tey5d2af632002-05-26 13:36:41 +000041
42def wrap_frame(frame):
43 fid = id(frame)
44 frametable[fid] = frame
45 return fid
46
47def wrap_info(info):
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000048 "replace info[2], a traceback instance, by its ID"
Chui Tey5d2af632002-05-26 13:36:41 +000049 if info is None:
50 return None
51 else:
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000052 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
Chui Tey5d2af632002-05-26 13:36:41 +000058
59class GUIProxy:
60
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000061 def __init__(self, conn, gui_adap_oid):
Chui Tey5d2af632002-05-26 13:36:41 +000062 self.conn = conn
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000063 self.oid = gui_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +000064
65 def interaction(self, message, frame, info=None):
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +000066 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000067 # pass frame and traceback object IDs instead of the objects themselves
Chui Tey5d2af632002-05-26 13:36:41 +000068 self.conn.remotecall(self.oid, "interaction",
69 (message, wrap_frame(frame), wrap_info(info)),
70 {})
71
72class IdbAdapter:
73
74 def __init__(self, idb):
75 self.idb = idb
76
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +000077 #----------called by an IdbProxy----------
78
Chui Tey5d2af632002-05-26 13:36:41 +000079 def set_step(self):
80 self.idb.set_step()
81
82 def set_quit(self):
83 self.idb.set_quit()
84
85 def set_continue(self):
86 self.idb.set_continue()
87
88 def set_next(self, fid):
89 frame = frametable[fid]
90 self.idb.set_next(frame)
91
92 def set_return(self, fid):
93 frame = frametable[fid]
94 self.idb.set_return(frame)
95
96 def get_stack(self, fid, tbid):
Walter Dörwald70a6b492004-02-12 17:35:32 +000097 ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid)
Chui Tey5d2af632002-05-26 13:36:41 +000098 frame = frametable[fid]
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000099 if tbid is None:
100 tb = None
101 else:
102 tb = tracebacktable[tbid]
Chui Tey5d2af632002-05-26 13:36:41 +0000103 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
108
109 def run(self, cmd):
110 import __main__
111 self.idb.run(cmd, __main__.__dict__)
112
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000113 def set_break(self, filename, lineno):
114 msg = self.idb.set_break(filename, lineno)
115 return msg
116
117 def clear_break(self, filename, lineno):
118 msg = self.idb.clear_break(filename, lineno)
Kurt B. Kaiser63857a42002-09-05 02:31:20 +0000119 return msg
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000120
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000121 def clear_all_file_breaks(self, filename):
122 msg = self.idb.clear_all_file_breaks(filename)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000123 return msg
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000124
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000125 #----------called by a FrameProxy----------
126
Chui Tey5d2af632002-05-26 13:36:41 +0000127 def frame_attr(self, fid, name):
128 frame = frametable[fid]
129 return getattr(frame, name)
130
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
137
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
144
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
151
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000152 #----------called by a CodeProxy----------
153
Chui Tey5d2af632002-05-26 13:36:41 +0000154 def code_name(self, cid):
155 code = codetable[cid]
156 return code.co_name
157
158 def code_filename(self, cid):
159 code = codetable[cid]
160 return code.co_filename
161
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000162 #----------called by a DictProxy----------
163
Chui Tey5d2af632002-05-26 13:36:41 +0000164 def dict_keys(self, did):
165 dict = dicttable[did]
166 return dict.keys()
167
168 def dict_item(self, did, key):
169 dict = dicttable[did]
170 value = dict[key]
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000171 value = repr(value)
Chui Tey5d2af632002-05-26 13:36:41 +0000172 return value
173
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000174#----------end class IdbAdapter----------
175
176
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000177def start_debugger(rpchandler, gui_adap_oid):
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000178 """Start the debugger and its RPC link in the Python subprocess
179
180 Start the subprocess side of the split debugger and set up that side of the
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000181 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000182 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.
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000185
186 """
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000187 gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000188 idb = Debugger.Idb(gui_proxy)
189 idb_adap = IdbAdapter(idb)
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000190 rpchandler.register(idb_adap_oid, idb_adap)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000191 return idb_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +0000192
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000193
194#=======================================
195#
196# In the IDLE process:
197
Chui Tey5d2af632002-05-26 13:36:41 +0000198
199class FrameProxy:
200
201 def __init__(self, conn, fid):
202 self._conn = conn
203 self._fid = fid
204 self._oid = "idb_adapter"
205 self._dictcache = {}
206
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), {})
218
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)
222
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)
227
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)
232
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
239
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000240
Chui Tey5d2af632002-05-26 13:36:41 +0000241class CodeProxy:
242
243 def __init__(self, conn, oid, cid):
244 self._conn = conn
245 self._oid = oid
246 self._cid = cid
247
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,), {})
255
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000256
Chui Tey5d2af632002-05-26 13:36:41 +0000257class DictProxy:
258
259 def __init__(self, conn, oid, did):
260 self._conn = conn
261 self._oid = oid
262 self._did = did
263
264 def keys(self):
265 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
266
267 def __getitem__(self, key):
268 return self._conn.remotecall(self._oid, "dict_item",
269 (self._did, key), {})
270
271 def __getattr__(self, name):
272 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
273 raise AttributeError, name
274
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000275
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000276class GUIAdapter:
Chui Tey5d2af632002-05-26 13:36:41 +0000277
278 def __init__(self, conn, gui):
279 self.conn = conn
280 self.gui = gui
281
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000282 def interaction(self, message, fid, modified_info):
283 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
Chui Tey5d2af632002-05-26 13:36:41 +0000284 frame = FrameProxy(self.conn, fid)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000285 self.gui.interaction(message, frame, modified_info)
Chui Tey5d2af632002-05-26 13:36:41 +0000286
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000287
Chui Tey5d2af632002-05-26 13:36:41 +0000288class IdbProxy:
289
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000290 def __init__(self, conn, shell, oid):
Chui Tey5d2af632002-05-26 13:36:41 +0000291 self.oid = oid
292 self.conn = conn
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000293 self.shell = shell
Chui Tey5d2af632002-05-26 13:36:41 +0000294
295 def call(self, methodname, *args, **kwargs):
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000296 ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
Chui Tey5d2af632002-05-26 13:36:41 +0000297 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
Walter Dörwald70a6b492004-02-12 17:35:32 +0000298 ##print "**IdbProxy.call %s returns %r" % (methodname, value)
Chui Tey5d2af632002-05-26 13:36:41 +0000299 return value
300
301 def run(self, cmd, locals):
302 # Ignores locals on purpose!
Kurt B. Kaiser9ec454e2003-05-12 02:33:47 +0000303 seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000304 self.shell.interp.active_seq = seq
Chui Tey5d2af632002-05-26 13:36:41 +0000305
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000306 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)
Chui Tey5d2af632002-05-26 13:36:41 +0000309 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
310 return stack, i
311
312 def set_continue(self):
313 self.call("set_continue")
314
315 def set_step(self):
316 self.call("set_step")
317
318 def set_next(self, frame):
319 self.call("set_next", frame._fid)
320
321 def set_return(self, frame):
322 self.call("set_return", frame._fid)
323
324 def set_quit(self):
325 self.call("set_quit")
326
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000327 def set_break(self, filename, lineno):
328 msg = self.call("set_break", filename, lineno)
329 return msg
330
331 def clear_break(self, filename, lineno):
332 msg = self.call("clear_break", filename, lineno)
Kurt B. Kaiser63857a42002-09-05 02:31:20 +0000333 return msg
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000334
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000335 def clear_all_file_breaks(self, filename):
336 msg = self.call("clear_all_file_breaks", filename)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000337 return msg
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000338
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000339def start_remote_debugger(rpcclt, pyshell):
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000340 """Start the subprocess debugger, initialize the debugger GUI and RPC link
341
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000342 Request the RPCServer start the Python subprocess debugger and link. Set
343 up the Idle side of the split debugger by instantiating the IdbProxy,
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000344 debugger GUI, and debugger GUIAdapter objects and linking them together.
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000345
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000346 Register the GUIAdapter with the RPCClient to handle debugger GUI
347 interaction requests coming from the subprocess debugger via the GUIProxy.
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000348
349 The IdbAdapter will pass execution and environment requests coming from the
350 Idle debugger GUI to the subprocess debugger via the IdbProxy.
351
352 """
Kurt B. Kaiser63857a42002-09-05 02:31:20 +0000353 global idb_adap_oid
354
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000355 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000356 (gui_adap_oid,), {})
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000357 idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000358 gui = Debugger.Debugger(pyshell, idb_proxy)
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000359 gui_adap = GUIAdapter(rpcclt, gui)
360 rpcclt.register(gui_adap_oid, gui_adap)
Chui Tey5d2af632002-05-26 13:36:41 +0000361 return gui
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000362
363def close_remote_debugger(rpcclt):
364 """Shut down subprocess debugger and Idle side of debugger RPC link
365
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().)
370
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000371 """
Kurt B. Kaiser63857a42002-09-05 02:31:20 +0000372 close_subprocess_debugger(rpcclt)
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000373 rpcclt.unregister(gui_adap_oid)
Kurt B. Kaiser63857a42002-09-05 02:31:20 +0000374
375def close_subprocess_debugger(rpcclt):
376 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
377
378def 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'