blob: 84e94df607a225ee8fa8429e884f0d5f709b6eca [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. Kaiser669f4c32002-06-20 04:01:47 +000030#=======================================
31#
32# In the PYTHON subprocess:
Chui Tey5d2af632002-05-26 13:36:41 +000033
34frametable = {}
35dicttable = {}
36codetable = {}
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000037tracebacktable = {}
Chui Tey5d2af632002-05-26 13:36:41 +000038
39def wrap_frame(frame):
40 fid = id(frame)
41 frametable[fid] = frame
42 return fid
43
44def wrap_info(info):
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000045 "replace info[2], a traceback instance, by its ID"
Chui Tey5d2af632002-05-26 13:36:41 +000046 if info is None:
47 return None
48 else:
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000049 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
Chui Tey5d2af632002-05-26 13:36:41 +000055
56class GUIProxy:
57
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000058 def __init__(self, conn, gui_adap_oid):
Chui Tey5d2af632002-05-26 13:36:41 +000059 self.conn = conn
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000060 self.oid = gui_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +000061
62 def interaction(self, message, frame, info=None):
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +000063 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000064 # pass frame and traceback object IDs instead of the objects themselves
Chui Tey5d2af632002-05-26 13:36:41 +000065 self.conn.remotecall(self.oid, "interaction",
66 (message, wrap_frame(frame), wrap_info(info)),
67 {})
68
69class IdbAdapter:
70
71 def __init__(self, idb):
72 self.idb = idb
73
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +000074 #----------called by an IdbProxy----------
75
Chui Tey5d2af632002-05-26 13:36:41 +000076 def set_step(self):
77 self.idb.set_step()
78
79 def set_quit(self):
80 self.idb.set_quit()
81
82 def set_continue(self):
83 self.idb.set_continue()
84
85 def set_next(self, fid):
86 frame = frametable[fid]
87 self.idb.set_next(frame)
88
89 def set_return(self, fid):
90 frame = frametable[fid]
91 self.idb.set_return(frame)
92
93 def get_stack(self, fid, tbid):
94 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
95 frame = frametable[fid]
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +000096 if tbid is None:
97 tb = None
98 else:
99 tb = tracebacktable[tbid]
Chui Tey5d2af632002-05-26 13:36:41 +0000100 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
105
106 def run(self, cmd):
107 import __main__
108 self.idb.run(cmd, __main__.__dict__)
109
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000110 def set_break(self, filename, lineno):
111 msg = self.idb.set_break(filename, lineno)
112 return msg
113
114 def clear_break(self, filename, lineno):
115 msg = self.idb.clear_break(filename, lineno)
116
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000117 def clear_all_file_breaks(self, filename):
118 msg = self.idb.clear_all_file_breaks(filename)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000119 return msg
120
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000121 #----------called by a FrameProxy----------
122
Chui Tey5d2af632002-05-26 13:36:41 +0000123 def frame_attr(self, fid, name):
124 frame = frametable[fid]
125 return getattr(frame, name)
126
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
133
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
140
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
147
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000148 #----------called by a CodeProxy----------
149
Chui Tey5d2af632002-05-26 13:36:41 +0000150 def code_name(self, cid):
151 code = codetable[cid]
152 return code.co_name
153
154 def code_filename(self, cid):
155 code = codetable[cid]
156 return code.co_filename
157
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000158 #----------called by a DictProxy----------
159
Chui Tey5d2af632002-05-26 13:36:41 +0000160 def dict_keys(self, did):
161 dict = dicttable[did]
162 return dict.keys()
163
164 def dict_item(self, did, key):
165 dict = dicttable[did]
166 value = dict[key]
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000167 value = repr(value)
Chui Tey5d2af632002-05-26 13:36:41 +0000168 return value
169
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000170#----------end class IdbAdapter----------
171
172
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000173def start_debugger(rpchandler, gui_adap_oid):
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000174 """Start the debugger and its RPC link in the Python subprocess
175
176 Start the subprocess side of the split debugger and set up that side of the
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000177 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000178 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.
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000181
182 """
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000183 gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000184 idb = Debugger.Idb(gui_proxy)
185 idb_adap = IdbAdapter(idb)
186 idb_adap_oid = "idb_adapter"
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000187 rpchandler.register(idb_adap_oid, idb_adap)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000188 return idb_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +0000189
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000190
191#=======================================
192#
193# In the IDLE process:
194
Chui Tey5d2af632002-05-26 13:36:41 +0000195
196class FrameProxy:
197
198 def __init__(self, conn, fid):
199 self._conn = conn
200 self._fid = fid
201 self._oid = "idb_adapter"
202 self._dictcache = {}
203
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), {})
215
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)
219
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)
224
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)
229
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
236
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000237
Chui Tey5d2af632002-05-26 13:36:41 +0000238class CodeProxy:
239
240 def __init__(self, conn, oid, cid):
241 self._conn = conn
242 self._oid = oid
243 self._cid = cid
244
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,), {})
252
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000253
Chui Tey5d2af632002-05-26 13:36:41 +0000254class DictProxy:
255
256 def __init__(self, conn, oid, did):
257 self._conn = conn
258 self._oid = oid
259 self._did = did
260
261 def keys(self):
262 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
263
264 def __getitem__(self, key):
265 return self._conn.remotecall(self._oid, "dict_item",
266 (self._did, key), {})
267
268 def __getattr__(self, name):
269 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
270 raise AttributeError, name
271
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000272
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000273class GUIAdapter:
Chui Tey5d2af632002-05-26 13:36:41 +0000274
275 def __init__(self, conn, gui):
276 self.conn = conn
277 self.gui = gui
278
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000279 def interaction(self, message, fid, modified_info):
280 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
Chui Tey5d2af632002-05-26 13:36:41 +0000281 frame = FrameProxy(self.conn, fid)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000282 self.gui.interaction(message, frame, modified_info)
Chui Tey5d2af632002-05-26 13:36:41 +0000283
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000284
Chui Tey5d2af632002-05-26 13:36:41 +0000285class IdbProxy:
286
287 def __init__(self, conn, oid):
288 self.oid = oid
289 self.conn = conn
290
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
296
297 def run(self, cmd, locals):
298 # Ignores locals on purpose!
299 self.call("run", cmd)
300
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000301 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)
Chui Tey5d2af632002-05-26 13:36:41 +0000304 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
305 return stack, i
306
307 def set_continue(self):
308 self.call("set_continue")
309
310 def set_step(self):
311 self.call("set_step")
312
313 def set_next(self, frame):
314 self.call("set_next", frame._fid)
315
316 def set_return(self, frame):
317 self.call("set_return", frame._fid)
318
319 def set_quit(self):
320 self.call("set_quit")
321
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000322 def set_break(self, filename, lineno):
323 msg = self.call("set_break", filename, lineno)
324 return msg
325
326 def clear_break(self, filename, lineno):
327 msg = self.call("clear_break", filename, lineno)
328
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000329 def clear_all_file_breaks(self, filename):
330 msg = self.call("clear_all_file_breaks", filename)
Kurt B. Kaiserf50d0f92002-07-03 03:55:43 +0000331 return msg
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000332
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000333def start_remote_debugger(rpcclt, pyshell):
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000334 """Start the subprocess debugger, initialize the debugger GUI and RPC link
335
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000336 Request the RPCServer start the Python subprocess debugger and link. Set
337 up the Idle side of the split debugger by instantiating the IdbProxy,
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000338 debugger GUI, and debugger GUIAdapter objects and linking them together.
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000339
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000340 Register the GUIAdapter with the RPCClient to handle debugger GUI
341 interaction requests coming from the subprocess debugger via the GUIProxy.
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000342
343 The IdbAdapter will pass execution and environment requests coming from the
344 Idle debugger GUI to the subprocess debugger via the IdbProxy.
345
346 """
347 gui_adap_oid = "gui_adapter"
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000348 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000349 (gui_adap_oid,), {})
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000350 idb_proxy = IdbProxy(rpcclt, idb_adap_oid)
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000351 gui = Debugger.Debugger(pyshell, idb_proxy)
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000352 gui_adap = GUIAdapter(rpcclt, gui)
353 rpcclt.register(gui_adap_oid, gui_adap)
Chui Tey5d2af632002-05-26 13:36:41 +0000354 return gui
Kurt B. Kaiserffd3a422002-06-26 02:32:09 +0000355
356def close_remote_debugger(rpcclt):
357 """Shut down subprocess debugger and Idle side of debugger RPC link
358
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().)
363
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)