1
"""Support for remote Python debugging.
3
Some ASCII art to describe the structure:
5
IN PYTHON SUBPROCESS # IN IDLE PROCESS
8
+----------+ # +------------+ +-----+
9
| GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
10
+-----+--calls-->+----------+ # +------------+ +-----+
12
+-----+<-calls--+------------+ # +----------+<--calls-/
13
| IdbAdapter |<--remote#call--| IdbProxy |
14
+------------+ # +----------+
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.
24
from idlelib import rpc
25
from idlelib import Debugger
29
idb_adap_oid = "idb_adapter"
30
gui_adap_oid = "gui_adapter"
32
#=======================================
34
# In the PYTHON subprocess:
41
def wrap_frame(frame):
43
frametable[fid] = frame
47
"replace info[2], a traceback instance, by its ID"
52
assert isinstance(traceback, types.TracebackType)
53
traceback_id = id(traceback)
54
tracebacktable[traceback_id] = traceback
55
modified_info = (info[0], info[1], traceback_id)
60
def __init__(self, conn, gui_adap_oid):
62
self.oid = gui_adap_oid
64
def interaction(self, message, frame, info=None):
65
# calls rpc.SocketIO.remotecall() via run.MyHandler instance
66
# pass frame and traceback object IDs instead of the objects themselves
67
self.conn.remotecall(self.oid, "interaction",
68
(message, wrap_frame(frame), wrap_info(info)),
73
def __init__(self, idb):
76
#----------called by an IdbProxy----------
84
def set_continue(self):
85
self.idb.set_continue()
87
def set_next(self, fid):
88
frame = frametable[fid]
89
self.idb.set_next(frame)
91
def set_return(self, fid):
92
frame = frametable[fid]
93
self.idb.set_return(frame)
95
def get_stack(self, fid, tbid):
96
frame = frametable[fid]
100
tb = tracebacktable[tbid]
101
stack, i = self.idb.get_stack(frame, tb)
102
stack = [(wrap_frame(frame), k) for frame, k in stack]
107
self.idb.run(cmd, __main__.__dict__)
109
def set_break(self, filename, lineno):
110
msg = self.idb.set_break(filename, lineno)
113
def clear_break(self, filename, lineno):
114
msg = self.idb.clear_break(filename, lineno)
117
def clear_all_file_breaks(self, filename):
118
msg = self.idb.clear_all_file_breaks(filename)
121
#----------called by a FrameProxy----------
123
def frame_attr(self, fid, name):
124
frame = frametable[fid]
125
return getattr(frame, name)
127
def frame_globals(self, fid):
128
frame = frametable[fid]
129
dict = frame.f_globals
131
dicttable[did] = dict
134
def frame_locals(self, fid):
135
frame = frametable[fid]
136
dict = frame.f_locals
138
dicttable[did] = dict
141
def frame_code(self, fid):
142
frame = frametable[fid]
145
codetable[cid] = code
148
#----------called by a CodeProxy----------
150
def code_name(self, cid):
151
code = codetable[cid]
154
def code_filename(self, cid):
155
code = codetable[cid]
156
return code.co_filename
158
#----------called by a DictProxy----------
160
def dict_keys(self, did):
161
raise NotImplemented("dict_keys not public or pickleable")
162
## dict = dicttable[did]
163
## return dict.keys()
165
### Needed until dict_keys is type is finished and pickealable.
166
### Will probably need to extend rpc.py:SocketIO._proxify at that time.
167
def dict_keys_list(self, did):
168
dict = dicttable[did]
169
return list(dict.keys())
171
def dict_item(self, did, key):
172
dict = dicttable[did]
174
value = repr(value) ### can't pickle module 'builtins'
177
#----------end class IdbAdapter----------
180
def start_debugger(rpchandler, gui_adap_oid):
181
"""Start the debugger and its RPC link in the Python subprocess
183
Start the subprocess side of the split debugger and set up that side of the
184
RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
185
objects and linking them together. Register the IdbAdapter with the
186
RPCServer to handle RPC requests from the split debugger GUI via the
190
gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
191
idb = Debugger.Idb(gui_proxy)
192
idb_adap = IdbAdapter(idb)
193
rpchandler.register(idb_adap_oid, idb_adap)
197
#=======================================
199
# In the IDLE process:
204
def __init__(self, conn, fid):
207
self._oid = "idb_adapter"
210
def __getattr__(self, name):
212
raise AttributeError(name)
214
return self._get_f_code()
215
if name == "f_globals":
216
return self._get_f_globals()
217
if name == "f_locals":
218
return self._get_f_locals()
219
return self._conn.remotecall(self._oid, "frame_attr",
220
(self._fid, name), {})
222
def _get_f_code(self):
223
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
224
return CodeProxy(self._conn, self._oid, cid)
226
def _get_f_globals(self):
227
did = self._conn.remotecall(self._oid, "frame_globals",
229
return self._get_dict_proxy(did)
231
def _get_f_locals(self):
232
did = self._conn.remotecall(self._oid, "frame_locals",
234
return self._get_dict_proxy(did)
236
def _get_dict_proxy(self, did):
237
if did in self._dictcache:
238
return self._dictcache[did]
239
dp = DictProxy(self._conn, self._oid, did)
240
self._dictcache[did] = dp
246
def __init__(self, conn, oid, cid):
251
def __getattr__(self, name):
252
if name == "co_name":
253
return self._conn.remotecall(self._oid, "code_name",
255
if name == "co_filename":
256
return self._conn.remotecall(self._oid, "code_filename",
262
def __init__(self, conn, oid, did):
268
## return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
270
# 'temporary' until dict_keys is a pickleable built-in type
272
return self._conn.remotecall(self._oid,
273
"dict_keys_list", (self._did,), {})
275
def __getitem__(self, key):
276
return self._conn.remotecall(self._oid, "dict_item",
277
(self._did, key), {})
279
def __getattr__(self, name):
280
##print("*** Failed DictProxy.__getattr__:", name)
281
raise AttributeError(name)
286
def __init__(self, conn, gui):
290
def interaction(self, message, fid, modified_info):
291
##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info))
292
frame = FrameProxy(self.conn, fid)
293
self.gui.interaction(message, frame, modified_info)
298
def __init__(self, conn, shell, oid):
303
def call(self, methodname, *args, **kwargs):
304
##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs))
305
value = self.conn.remotecall(self.oid, methodname, args, kwargs)
306
##print("*** IdbProxy.call %s returns %r" % (methodname, value))
309
def run(self, cmd, locals):
310
# Ignores locals on purpose!
311
seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
312
self.shell.interp.active_seq = seq
314
def get_stack(self, frame, tbid):
315
# passing frame and traceback IDs, not the objects themselves
316
stack, i = self.call("get_stack", frame._fid, tbid)
317
stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
320
def set_continue(self):
321
self.call("set_continue")
324
self.call("set_step")
326
def set_next(self, frame):
327
self.call("set_next", frame._fid)
329
def set_return(self, frame):
330
self.call("set_return", frame._fid)
333
self.call("set_quit")
335
def set_break(self, filename, lineno):
336
msg = self.call("set_break", filename, lineno)
339
def clear_break(self, filename, lineno):
340
msg = self.call("clear_break", filename, lineno)
343
def clear_all_file_breaks(self, filename):
344
msg = self.call("clear_all_file_breaks", filename)
347
def start_remote_debugger(rpcclt, pyshell):
348
"""Start the subprocess debugger, initialize the debugger GUI and RPC link
350
Request the RPCServer start the Python subprocess debugger and link. Set
351
up the Idle side of the split debugger by instantiating the IdbProxy,
352
debugger GUI, and debugger GUIAdapter objects and linking them together.
354
Register the GUIAdapter with the RPCClient to handle debugger GUI
355
interaction requests coming from the subprocess debugger via the GUIProxy.
357
The IdbAdapter will pass execution and environment requests coming from the
358
Idle debugger GUI to the subprocess debugger via the IdbProxy.
363
idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
365
idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
366
gui = Debugger.Debugger(pyshell, idb_proxy)
367
gui_adap = GUIAdapter(rpcclt, gui)
368
rpcclt.register(gui_adap_oid, gui_adap)
371
def close_remote_debugger(rpcclt):
372
"""Shut down subprocess debugger and Idle side of debugger RPC link
374
Request that the RPCServer shut down the subprocess debugger and link.
375
Unregister the GUIAdapter, which will cause a GC on the Idle process
376
debugger and RPC link objects. (The second reference to the debugger GUI
377
is deleted in PyShell.close_remote_debugger().)
380
close_subprocess_debugger(rpcclt)
381
rpcclt.unregister(gui_adap_oid)
383
def close_subprocess_debugger(rpcclt):
384
rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
386
def restart_subprocess_debugger(rpcclt):
387
idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
389
assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'