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.
30 idb_adap_oid
= "idb_adapter"
31 gui_adap_oid
= "gui_adapter"
33 #=======================================
35 # In the PYTHON subprocess:
42 def wrap_frame(frame
):
44 frametable
[fid
] = frame
48 "replace info[2], a traceback instance, by its ID"
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
)
61 def __init__(self
, conn
, gui_adap_oid
):
63 self
.oid
= gui_adap_oid
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
)),
74 def __init__(self
, idb
):
77 #----------called by an IdbProxy----------
85 def set_continue(self
):
86 self
.idb
.set_continue()
88 def set_next(self
, fid
):
89 frame
= frametable
[fid
]
90 self
.idb
.set_next(frame
)
92 def set_return(self
, fid
):
93 frame
= frametable
[fid
]
94 self
.idb
.set_return(frame
)
96 def get_stack(self
, fid
, tbid
):
97 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
98 frame
= frametable
[fid
]
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
111 self
.idb
.run(cmd
, __main__
.__dict
__)
113 def set_break(self
, filename
, lineno
):
114 msg
= self
.idb
.set_break(filename
, lineno
)
117 def clear_break(self
, filename
, lineno
):
118 msg
= self
.idb
.clear_break(filename
, lineno
)
121 def clear_all_file_breaks(self
, filename
):
122 msg
= self
.idb
.clear_all_file_breaks(filename
)
125 #----------called by a FrameProxy----------
127 def frame_attr(self
, fid
, name
):
128 frame
= frametable
[fid
]
129 return getattr(frame
, name
)
131 def frame_globals(self
, fid
):
132 frame
= frametable
[fid
]
133 dict = frame
.f_globals
135 dicttable
[did
] = dict
138 def frame_locals(self
, fid
):
139 frame
= frametable
[fid
]
140 dict = frame
.f_locals
142 dicttable
[did
] = dict
145 def frame_code(self
, fid
):
146 frame
= frametable
[fid
]
149 codetable
[cid
] = code
152 #----------called by a CodeProxy----------
154 def code_name(self
, cid
):
155 code
= codetable
[cid
]
158 def code_filename(self
, cid
):
159 code
= codetable
[cid
]
160 return code
.co_filename
162 #----------called by a DictProxy----------
164 def dict_keys(self
, did
):
165 dict = dicttable
[did
]
168 def dict_item(self
, did
, key
):
169 dict = dicttable
[did
]
174 #----------end class IdbAdapter----------
177 def start_debugger(rpchandler
, gui_adap_oid
):
178 """Start the debugger and its RPC link in the Python subprocess
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
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
)
194 #=======================================
196 # In the IDLE process:
201 def __init__(self
, conn
, fid
):
204 self
._oid
= "idb_adapter"
207 def __getattr__(self
, name
):
209 raise AttributeError, name
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
), {})
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
)
223 def _get_f_globals(self
):
224 did
= self
._conn
.remotecall(self
._oid
, "frame_globals",
226 return self
._get
_dict
_proxy
(did
)
228 def _get_f_locals(self
):
229 did
= self
._conn
.remotecall(self
._oid
, "frame_locals",
231 return self
._get
_dict
_proxy
(did
)
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
243 def __init__(self
, conn
, oid
, cid
):
248 def __getattr__(self
, name
):
249 if name
== "co_name":
250 return self
._conn
.remotecall(self
._oid
, "code_name",
252 if name
== "co_filename":
253 return self
._conn
.remotecall(self
._oid
, "code_filename",
259 def __init__(self
, conn
, oid
, did
):
265 return self
._conn
.remotecall(self
._oid
, "dict_keys", (self
._did
,), {})
267 def __getitem__(self
, key
):
268 return self
._conn
.remotecall(self
._oid
, "dict_item",
269 (self
._did
, key
), {})
271 def __getattr__(self
, name
):
272 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
273 raise AttributeError, name
278 def __init__(self
, conn
, gui
):
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
)
290 def __init__(self
, conn
, shell
, oid
):
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 %s" % (methodname, `value`)
301 def run(self
, cmd
, locals):
302 # Ignores locals on purpose!
303 seq
= self
.conn
.asynccall(self
.oid
, "run", (cmd
,), {})
304 self
.shell
.interp
.active_seq
= seq
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
]
312 def set_continue(self
):
313 self
.call("set_continue")
316 self
.call("set_step")
318 def set_next(self
, frame
):
319 self
.call("set_next", frame
._fid
)
321 def set_return(self
, frame
):
322 self
.call("set_return", frame
._fid
)
325 self
.call("set_quit")
327 def set_break(self
, filename
, lineno
):
328 msg
= self
.call("set_break", filename
, lineno
)
331 def clear_break(self
, filename
, lineno
):
332 msg
= self
.call("clear_break", filename
, lineno
)
335 def clear_all_file_breaks(self
, filename
):
336 msg
= self
.call("clear_all_file_breaks", filename
)
339 def start_remote_debugger(rpcclt
, pyshell
):
340 """Start the subprocess debugger, initialize the debugger GUI and RPC link
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.
346 Register the GUIAdapter with the RPCClient to handle debugger GUI
347 interaction requests coming from the subprocess debugger via the GUIProxy.
349 The IdbAdapter will pass execution and environment requests coming from the
350 Idle debugger GUI to the subprocess debugger via the IdbProxy.
355 idb_adap_oid
= rpcclt
.remotecall("exec", "start_the_debugger",\
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
)
363 def close_remote_debugger(rpcclt
):
364 """Shut down subprocess debugger and Idle side of debugger RPC link
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().)
372 close_subprocess_debugger(rpcclt
)
373 rpcclt
.unregister(gui_adap_oid
)
375 def close_subprocess_debugger(rpcclt
):
376 rpcclt
.remotecall("exec", "stop_the_debugger", (idb_adap_oid
,), {})
378 def restart_subprocess_debugger(rpcclt
):
379 idb_adap_oid_ret
= rpcclt
.remotecall("exec", "start_the_debugger",\
381 assert idb_adap_oid_ret
== idb_adap_oid
, 'Idb restarted with different oid'