blob: 5131065151ad674413efc6ac9e583d8a07f3788e [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
24import rpc
25import Debugger
26
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000027debugging = 0
28
Chui Tey5d2af632002-05-26 13:36:41 +000029# In the PYTHON subprocess
30
31frametable = {}
32dicttable = {}
33codetable = {}
34
35def wrap_frame(frame):
36 fid = id(frame)
37 frametable[fid] = frame
38 return fid
39
40def wrap_info(info):
41 if info is None:
42 return None
43 else:
44 return None # XXX for now
45
46class GUIProxy:
47
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000048 def __init__(self, conn, gui_adap_oid):
Chui Tey5d2af632002-05-26 13:36:41 +000049 self.conn = conn
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000050 self.oid = gui_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +000051
52 def interaction(self, message, frame, info=None):
53 self.conn.remotecall(self.oid, "interaction",
54 (message, wrap_frame(frame), wrap_info(info)),
55 {})
56
57class IdbAdapter:
58
59 def __init__(self, idb):
60 self.idb = idb
61
62 def set_step(self):
63 self.idb.set_step()
64
65 def set_quit(self):
66 self.idb.set_quit()
67
68 def set_continue(self):
69 self.idb.set_continue()
70
71 def set_next(self, fid):
72 frame = frametable[fid]
73 self.idb.set_next(frame)
74
75 def set_return(self, fid):
76 frame = frametable[fid]
77 self.idb.set_return(frame)
78
79 def get_stack(self, fid, tbid):
80 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
81 frame = frametable[fid]
82 tb = None # XXX for now
83 stack, i = self.idb.get_stack(frame, tb)
84 ##print >>sys.__stderr__, "get_stack() ->", stack
85 stack = [(wrap_frame(frame), k) for frame, k in stack]
86 ##print >>sys.__stderr__, "get_stack() ->", stack
87 return stack, i
88
89 def run(self, cmd):
90 import __main__
91 self.idb.run(cmd, __main__.__dict__)
92
93 def frame_attr(self, fid, name):
94 frame = frametable[fid]
95 return getattr(frame, name)
96
97 def frame_globals(self, fid):
98 frame = frametable[fid]
99 dict = frame.f_globals
100 did = id(dict)
101 dicttable[did] = dict
102 return did
103
104 def frame_locals(self, fid):
105 frame = frametable[fid]
106 dict = frame.f_locals
107 did = id(dict)
108 dicttable[did] = dict
109 return did
110
111 def frame_code(self, fid):
112 frame = frametable[fid]
113 code = frame.f_code
114 cid = id(code)
115 codetable[cid] = code
116 return cid
117
118 def code_name(self, cid):
119 code = codetable[cid]
120 return code.co_name
121
122 def code_filename(self, cid):
123 code = codetable[cid]
124 return code.co_filename
125
126 def dict_keys(self, did):
127 dict = dicttable[did]
128 return dict.keys()
129
130 def dict_item(self, did, key):
131 dict = dicttable[did]
132 value = dict[key]
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000133 value = repr(value)
134# try:
135# # Test for picklability
136# import cPickle
137# pklstr = cPickle.dumps(value)
138# except:
139# print >>sys.__stderr__, "** dict_item pickle failed: ", value
140# raise
141# #value = None
Chui Tey5d2af632002-05-26 13:36:41 +0000142 return value
143
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000144def start_debugger(conn, gui_adap_oid):
145 "Launch debugger in the remote python subprocess"
146 gui_proxy = GUIProxy(conn, gui_adap_oid)
147 idb = Debugger.Idb(gui_proxy)
148 idb_adap = IdbAdapter(idb)
149 idb_adap_oid = "idb_adapter"
150 conn.register(idb_adap_oid, idb_adap)
151 return idb_adap_oid
Chui Tey5d2af632002-05-26 13:36:41 +0000152
153# In the IDLE process
154
155class FrameProxy:
156
157 def __init__(self, conn, fid):
158 self._conn = conn
159 self._fid = fid
160 self._oid = "idb_adapter"
161 self._dictcache = {}
162
163 def __getattr__(self, name):
164 if name[:1] == "_":
165 raise AttributeError, name
166 if name == "f_code":
167 return self._get_f_code()
168 if name == "f_globals":
169 return self._get_f_globals()
170 if name == "f_locals":
171 return self._get_f_locals()
172 return self._conn.remotecall(self._oid, "frame_attr",
173 (self._fid, name), {})
174
175 def _get_f_code(self):
176 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
177 return CodeProxy(self._conn, self._oid, cid)
178
179 def _get_f_globals(self):
180 did = self._conn.remotecall(self._oid, "frame_globals",
181 (self._fid,), {})
182 return self._get_dict_proxy(did)
183
184 def _get_f_locals(self):
185 did = self._conn.remotecall(self._oid, "frame_locals",
186 (self._fid,), {})
187 return self._get_dict_proxy(did)
188
189 def _get_dict_proxy(self, did):
190 if self._dictcache.has_key(did):
191 return self._dictcache[did]
192 dp = DictProxy(self._conn, self._oid, did)
193 self._dictcache[did] = dp
194 return dp
195
196class CodeProxy:
197
198 def __init__(self, conn, oid, cid):
199 self._conn = conn
200 self._oid = oid
201 self._cid = cid
202
203 def __getattr__(self, name):
204 if name == "co_name":
205 return self._conn.remotecall(self._oid, "code_name",
206 (self._cid,), {})
207 if name == "co_filename":
208 return self._conn.remotecall(self._oid, "code_filename",
209 (self._cid,), {})
210
211class DictProxy:
212
213 def __init__(self, conn, oid, did):
214 self._conn = conn
215 self._oid = oid
216 self._did = did
217
218 def keys(self):
219 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
220
221 def __getitem__(self, key):
222 return self._conn.remotecall(self._oid, "dict_item",
223 (self._did, key), {})
224
225 def __getattr__(self, name):
226 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
227 raise AttributeError, name
228
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000229class GUIAdapter:
Chui Tey5d2af632002-05-26 13:36:41 +0000230
231 def __init__(self, conn, gui):
232 self.conn = conn
233 self.gui = gui
234
235 def interaction(self, message, fid, iid):
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000236 ##print "interaction: (%s, %s, %s)" % (`message`,`fid`, `iid`)
Chui Tey5d2af632002-05-26 13:36:41 +0000237 frame = FrameProxy(self.conn, fid)
238 info = None # XXX for now
239 self.gui.interaction(message, frame, info)
240
241class IdbProxy:
242
243 def __init__(self, conn, oid):
244 self.oid = oid
245 self.conn = conn
246
247 def call(self, methodname, *args, **kwargs):
248 ##print "call %s %s %s" % (methodname, args, kwargs)
249 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
250 ##print "return %s" % `value`
251 return value
252
253 def run(self, cmd, locals):
254 # Ignores locals on purpose!
255 self.call("run", cmd)
256
257 def get_stack(self, frame, tb):
258 stack, i = self.call("get_stack", frame._fid, None)
259 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
260 return stack, i
261
262 def set_continue(self):
263 self.call("set_continue")
264
265 def set_step(self):
266 self.call("set_step")
267
268 def set_next(self, frame):
269 self.call("set_next", frame._fid)
270
271 def set_return(self, frame):
272 self.call("set_return", frame._fid)
273
274 def set_quit(self):
275 self.call("set_quit")
276
277def start_remote_debugger(conn, pyshell):
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000278 """Start the subprocess debugger, initialize the debugger GUI and RPC link
279
280 Start the debugger in the remote Python process. Instantiate IdbProxy,
281 Debugger GUI, and Debugger GUIAdapter objects, and link them together.
282
283 The GUIAdapter will handle debugger GUI interaction requests coming from
284 the subprocess debugger via the GUIProxy.
285
286 The IdbAdapter will pass execution and environment requests coming from the
287 Idle debugger GUI to the subprocess debugger via the IdbProxy.
288
289 """
290 gui_adap_oid = "gui_adapter"
291 idb_adap_oid = conn.remotecall("exec", "start_the_debugger",\
292 (gui_adap_oid,), {})
293 idb_proxy = IdbProxy(conn, idb_adap_oid)
294 gui = Debugger.Debugger(pyshell, idb_proxy)
295 gui_adap = GUIAdapter(conn, gui)
296 conn.register(gui_adap_oid, gui_adap)
Chui Tey5d2af632002-05-26 13:36:41 +0000297 return gui