blob: 0f8eac5d13866b19c523e605a2ff09b7e6284db3 [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
27# In the PYTHON subprocess
28
29frametable = {}
30dicttable = {}
31codetable = {}
32
33def wrap_frame(frame):
34 fid = id(frame)
35 frametable[fid] = frame
36 return fid
37
38def wrap_info(info):
39 if info is None:
40 return None
41 else:
42 return None # XXX for now
43
44class GUIProxy:
45
46 def __init__(self, conn, oid):
47 self.conn = conn
48 self.oid = oid
49
50 def interaction(self, message, frame, info=None):
51 self.conn.remotecall(self.oid, "interaction",
52 (message, wrap_frame(frame), wrap_info(info)),
53 {})
54
55class IdbAdapter:
56
57 def __init__(self, idb):
58 self.idb = idb
59
60 def set_step(self):
61 self.idb.set_step()
62
63 def set_quit(self):
64 self.idb.set_quit()
65
66 def set_continue(self):
67 self.idb.set_continue()
68
69 def set_next(self, fid):
70 frame = frametable[fid]
71 self.idb.set_next(frame)
72
73 def set_return(self, fid):
74 frame = frametable[fid]
75 self.idb.set_return(frame)
76
77 def get_stack(self, fid, tbid):
78 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
79 frame = frametable[fid]
80 tb = None # XXX for now
81 stack, i = self.idb.get_stack(frame, tb)
82 ##print >>sys.__stderr__, "get_stack() ->", stack
83 stack = [(wrap_frame(frame), k) for frame, k in stack]
84 ##print >>sys.__stderr__, "get_stack() ->", stack
85 return stack, i
86
87 def run(self, cmd):
88 import __main__
89 self.idb.run(cmd, __main__.__dict__)
90
91 def frame_attr(self, fid, name):
92 frame = frametable[fid]
93 return getattr(frame, name)
94
95 def frame_globals(self, fid):
96 frame = frametable[fid]
97 dict = frame.f_globals
98 did = id(dict)
99 dicttable[did] = dict
100 return did
101
102 def frame_locals(self, fid):
103 frame = frametable[fid]
104 dict = frame.f_locals
105 did = id(dict)
106 dicttable[did] = dict
107 return did
108
109 def frame_code(self, fid):
110 frame = frametable[fid]
111 code = frame.f_code
112 cid = id(code)
113 codetable[cid] = code
114 return cid
115
116 def code_name(self, cid):
117 code = codetable[cid]
118 return code.co_name
119
120 def code_filename(self, cid):
121 code = codetable[cid]
122 return code.co_filename
123
124 def dict_keys(self, did):
125 dict = dicttable[did]
126 return dict.keys()
127
128 def dict_item(self, did, key):
129 dict = dicttable[did]
130 value = dict[key]
131 try:
132 # Test for picklability
133 import cPickle
134 cPickle.dumps(value)
135 except:
136 value = None
137 return value
138
139def start_debugger(conn, gui_oid):
140 #
141 # launched in the python subprocess
142 #
143 gui = GUIProxy(conn, gui_oid)
144 idb = Debugger.Idb(gui)
145 ada = IdbAdapter(idb)
146 ada_oid = "idb_adapter"
147 conn.register(ada_oid, ada)
148 return ada_oid
149
150# In the IDLE process
151
152class FrameProxy:
153
154 def __init__(self, conn, fid):
155 self._conn = conn
156 self._fid = fid
157 self._oid = "idb_adapter"
158 self._dictcache = {}
159
160 def __getattr__(self, name):
161 if name[:1] == "_":
162 raise AttributeError, name
163 if name == "f_code":
164 return self._get_f_code()
165 if name == "f_globals":
166 return self._get_f_globals()
167 if name == "f_locals":
168 return self._get_f_locals()
169 return self._conn.remotecall(self._oid, "frame_attr",
170 (self._fid, name), {})
171
172 def _get_f_code(self):
173 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
174 return CodeProxy(self._conn, self._oid, cid)
175
176 def _get_f_globals(self):
177 did = self._conn.remotecall(self._oid, "frame_globals",
178 (self._fid,), {})
179 return self._get_dict_proxy(did)
180
181 def _get_f_locals(self):
182 did = self._conn.remotecall(self._oid, "frame_locals",
183 (self._fid,), {})
184 return self._get_dict_proxy(did)
185
186 def _get_dict_proxy(self, did):
187 if self._dictcache.has_key(did):
188 return self._dictcache[did]
189 dp = DictProxy(self._conn, self._oid, did)
190 self._dictcache[did] = dp
191 return dp
192
193class CodeProxy:
194
195 def __init__(self, conn, oid, cid):
196 self._conn = conn
197 self._oid = oid
198 self._cid = cid
199
200 def __getattr__(self, name):
201 if name == "co_name":
202 return self._conn.remotecall(self._oid, "code_name",
203 (self._cid,), {})
204 if name == "co_filename":
205 return self._conn.remotecall(self._oid, "code_filename",
206 (self._cid,), {})
207
208class DictProxy:
209
210 def __init__(self, conn, oid, did):
211 self._conn = conn
212 self._oid = oid
213 self._did = did
214
215 def keys(self):
216 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
217
218 def __getitem__(self, key):
219 return self._conn.remotecall(self._oid, "dict_item",
220 (self._did, key), {})
221
222 def __getattr__(self, name):
223 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
224 raise AttributeError, name
225
226class GUIAdaper:
227
228 def __init__(self, conn, gui):
229 self.conn = conn
230 self.gui = gui
231
232 def interaction(self, message, fid, iid):
233 print "interaction(%s, %s, %s)" % (`message`, `fid`, `iid`)
234 frame = FrameProxy(self.conn, fid)
235 info = None # XXX for now
236 self.gui.interaction(message, frame, info)
237
238class IdbProxy:
239
240 def __init__(self, conn, oid):
241 self.oid = oid
242 self.conn = conn
243
244 def call(self, methodname, *args, **kwargs):
245 ##print "call %s %s %s" % (methodname, args, kwargs)
246 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
247 ##print "return %s" % `value`
248 return value
249
250 def run(self, cmd, locals):
251 # Ignores locals on purpose!
252 self.call("run", cmd)
253
254 def get_stack(self, frame, tb):
255 stack, i = self.call("get_stack", frame._fid, None)
256 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
257 return stack, i
258
259 def set_continue(self):
260 self.call("set_continue")
261
262 def set_step(self):
263 self.call("set_step")
264
265 def set_next(self, frame):
266 self.call("set_next", frame._fid)
267
268 def set_return(self, frame):
269 self.call("set_return", frame._fid)
270
271 def set_quit(self):
272 self.call("set_quit")
273
274def start_remote_debugger(conn, pyshell):
275 #
276 # instruct the (remote) subprocess to create
277 # a debugger instance, and lets it know that
278 # the local GUIAdapter called "gui_adapter"
279 # is waiting notification of debugging events
280 #
281 ada_oid = "gui_adapter"
282 idb_oid = conn.remotecall("exec", "start_debugger", (ada_oid,), {})
283 idb = IdbProxy(conn, idb_oid)
284 gui = Debugger.Debugger(pyshell, idb)
285 ada = GUIAdaper(conn, gui)
286 conn.register(ada_oid, ada)
287 return gui