Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """Support for remote Python debugging. |
2 | ||
3 | Some 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 | ||
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. | |
20 | ||
21 | """ | |
22 | ||
23 | import sys | |
24 | import types | |
25 | import rpc | |
26 | import Debugger | |
27 | ||
28 | debugging = 0 | |
29 | ||
30 | idb_adap_oid = "idb_adapter" | |
31 | gui_adap_oid = "gui_adapter" | |
32 | ||
33 | #======================================= | |
34 | # | |
35 | # In the PYTHON subprocess: | |
36 | ||
37 | frametable = {} | |
38 | dicttable = {} | |
39 | codetable = {} | |
40 | tracebacktable = {} | |
41 | ||
42 | def wrap_frame(frame): | |
43 | fid = id(frame) | |
44 | frametable[fid] = frame | |
45 | return fid | |
46 | ||
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 | |
58 | ||
59 | class GUIProxy: | |
60 | ||
61 | def __init__(self, conn, gui_adap_oid): | |
62 | self.conn = conn | |
63 | self.oid = gui_adap_oid | |
64 | ||
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 | {}) | |
71 | ||
72 | class IdbAdapter: | |
73 | ||
74 | def __init__(self, idb): | |
75 | self.idb = idb | |
76 | ||
77 | #----------called by an IdbProxy---------- | |
78 | ||
79 | 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): | |
97 | ##print >>sys.__stderr__, "get_stack(%r, %r)" % (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 | |
108 | ||
109 | def run(self, cmd): | |
110 | import __main__ | |
111 | self.idb.run(cmd, __main__.__dict__) | |
112 | ||
113 | 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) | |
119 | return msg | |
120 | ||
121 | def clear_all_file_breaks(self, filename): | |
122 | msg = self.idb.clear_all_file_breaks(filename) | |
123 | return msg | |
124 | ||
125 | #----------called by a FrameProxy---------- | |
126 | ||
127 | 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 | ||
152 | #----------called by a CodeProxy---------- | |
153 | ||
154 | 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 | ||
162 | #----------called by a DictProxy---------- | |
163 | ||
164 | 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] | |
171 | value = repr(value) | |
172 | return value | |
173 | ||
174 | #----------end class IdbAdapter---------- | |
175 | ||
176 | ||
177 | def start_debugger(rpchandler, gui_adap_oid): | |
178 | """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 | |
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. | |
185 | ||
186 | """ | |
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 | |
192 | ||
193 | ||
194 | #======================================= | |
195 | # | |
196 | # In the IDLE process: | |
197 | ||
198 | ||
199 | class 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 | ||
240 | ||
241 | class 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 | ||
256 | ||
257 | class 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 | ||
275 | ||
276 | class GUIAdapter: | |
277 | ||
278 | def __init__(self, conn, gui): | |
279 | self.conn = conn | |
280 | self.gui = gui | |
281 | ||
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) | |
286 | ||
287 | ||
288 | class IdbProxy: | |
289 | ||
290 | def __init__(self, conn, shell, oid): | |
291 | self.oid = oid | |
292 | self.conn = conn | |
293 | self.shell = shell | |
294 | ||
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 %r" % (methodname, value) | |
299 | return value | |
300 | ||
301 | def run(self, cmd, locals): | |
302 | # Ignores locals on purpose! | |
303 | seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {}) | |
304 | self.shell.interp.active_seq = seq | |
305 | ||
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 | |
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 | ||
327 | 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) | |
333 | return msg | |
334 | ||
335 | def clear_all_file_breaks(self, filename): | |
336 | msg = self.call("clear_all_file_breaks", filename) | |
337 | return msg | |
338 | ||
339 | def start_remote_debugger(rpcclt, pyshell): | |
340 | """Start the subprocess debugger, initialize the debugger GUI and RPC link | |
341 | ||
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. | |
345 | ||
346 | Register the GUIAdapter with the RPCClient to handle debugger GUI | |
347 | interaction requests coming from the subprocess debugger via the GUIProxy. | |
348 | ||
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 | """ | |
353 | global idb_adap_oid | |
354 | ||
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 | |
362 | ||
363 | def 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 | ||
371 | """ | |
372 | close_subprocess_debugger(rpcclt) | |
373 | rpcclt.unregister(gui_adap_oid) | |
374 | ||
375 | def close_subprocess_debugger(rpcclt): | |
376 | rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) | |
377 | ||
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' |