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.
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 ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid)
97 frame
= frametable
[fid
]
101 tb
= tracebacktable
[tbid
]
102 stack
, i
= self
.idb
.get_stack(frame
, tb
)
103 ##print >>sys.__stderr__, "get_stack() ->", stack
104 stack
= [(wrap_frame(frame
), k
) for frame
, k
in stack
]
105 ##print >>sys.__stderr__, "get_stack() ->", stack
110 self
.idb
.run(cmd
, __main__
.__dict
__)
112 def set_break(self
, filename
, lineno
):
113 msg
= self
.idb
.set_break(filename
, lineno
)
116 def clear_break(self
, filename
, lineno
):
117 msg
= self
.idb
.clear_break(filename
, lineno
)
120 def clear_all_file_breaks(self
, filename
):
121 msg
= self
.idb
.clear_all_file_breaks(filename
)
124 #----------called by a FrameProxy----------
126 def frame_attr(self
, fid
, name
):
127 frame
= frametable
[fid
]
128 return getattr(frame
, name
)
130 def frame_globals(self
, fid
):
131 frame
= frametable
[fid
]
132 dict = frame
.f_globals
134 dicttable
[did
] = dict
137 def frame_locals(self
, fid
):
138 frame
= frametable
[fid
]
139 dict = frame
.f_locals
141 dicttable
[did
] = dict
144 def frame_code(self
, fid
):
145 frame
= frametable
[fid
]
148 codetable
[cid
] = code
151 #----------called by a CodeProxy----------
153 def code_name(self
, cid
):
154 code
= codetable
[cid
]
157 def code_filename(self
, cid
):
158 code
= codetable
[cid
]
159 return code
.co_filename
161 #----------called by a DictProxy----------
163 def dict_keys(self
, did
):
164 dict = dicttable
[did
]
167 def dict_item(self
, did
, key
):
168 dict = dicttable
[did
]
173 #----------end class IdbAdapter----------
176 def start_debugger(rpchandler
, gui_adap_oid
):
177 """Start the debugger and its RPC link in the Python subprocess
179 Start the subprocess side of the split debugger and set up that side of the
180 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
181 objects and linking them together. Register the IdbAdapter with the
182 RPCServer to handle RPC requests from the split debugger GUI via the
186 gui_proxy
= GUIProxy(rpchandler
, gui_adap_oid
)
187 idb
= Debugger
.Idb(gui_proxy
)
188 idb_adap
= IdbAdapter(idb
)
189 rpchandler
.register(idb_adap_oid
, idb_adap
)
193 #=======================================
195 # In the IDLE process:
200 def __init__(self
, conn
, fid
):
203 self
._oid
= "idb_adapter"
206 def __getattr__(self
, name
):
208 raise AttributeError, name
210 return self
._get
_f
_code
()
211 if name
== "f_globals":
212 return self
._get
_f
_globals
()
213 if name
== "f_locals":
214 return self
._get
_f
_locals
()
215 return self
._conn
.remotecall(self
._oid
, "frame_attr",
216 (self
._fid
, name
), {})
218 def _get_f_code(self
):
219 cid
= self
._conn
.remotecall(self
._oid
, "frame_code", (self
._fid
,), {})
220 return CodeProxy(self
._conn
, self
._oid
, cid
)
222 def _get_f_globals(self
):
223 did
= self
._conn
.remotecall(self
._oid
, "frame_globals",
225 return self
._get
_dict
_proxy
(did
)
227 def _get_f_locals(self
):
228 did
= self
._conn
.remotecall(self
._oid
, "frame_locals",
230 return self
._get
_dict
_proxy
(did
)
232 def _get_dict_proxy(self
, did
):
233 if did
in self
._dictcache
:
234 return self
._dictcache
[did
]
235 dp
= DictProxy(self
._conn
, self
._oid
, did
)
236 self
._dictcache
[did
] = dp
242 def __init__(self
, conn
, oid
, cid
):
247 def __getattr__(self
, name
):
248 if name
== "co_name":
249 return self
._conn
.remotecall(self
._oid
, "code_name",
251 if name
== "co_filename":
252 return self
._conn
.remotecall(self
._oid
, "code_filename",
258 def __init__(self
, conn
, oid
, did
):
264 return self
._conn
.remotecall(self
._oid
, "dict_keys", (self
._did
,), {})
266 def __getitem__(self
, key
):
267 return self
._conn
.remotecall(self
._oid
, "dict_item",
268 (self
._did
, key
), {})
270 def __getattr__(self
, name
):
271 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
272 raise AttributeError, name
277 def __init__(self
, conn
, gui
):
281 def interaction(self
, message
, fid
, modified_info
):
282 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
283 frame
= FrameProxy(self
.conn
, fid
)
284 self
.gui
.interaction(message
, frame
, modified_info
)
289 def __init__(self
, conn
, shell
, oid
):
294 def call(self
, methodname
, *args
, **kwargs
):
295 ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
296 value
= self
.conn
.remotecall(self
.oid
, methodname
, args
, kwargs
)
297 ##print "**IdbProxy.call %s returns %r" % (methodname, value)
300 def run(self
, cmd
, locals):
301 # Ignores locals on purpose!
302 seq
= self
.conn
.asyncqueue(self
.oid
, "run", (cmd
,), {})
303 self
.shell
.interp
.active_seq
= seq
305 def get_stack(self
, frame
, tbid
):
306 # passing frame and traceback IDs, not the objects themselves
307 stack
, i
= self
.call("get_stack", frame
._fid
, tbid
)
308 stack
= [(FrameProxy(self
.conn
, fid
), k
) for fid
, k
in stack
]
311 def set_continue(self
):
312 self
.call("set_continue")
315 self
.call("set_step")
317 def set_next(self
, frame
):
318 self
.call("set_next", frame
._fid
)
320 def set_return(self
, frame
):
321 self
.call("set_return", frame
._fid
)
324 self
.call("set_quit")
326 def set_break(self
, filename
, lineno
):
327 msg
= self
.call("set_break", filename
, lineno
)
330 def clear_break(self
, filename
, lineno
):
331 msg
= self
.call("clear_break", filename
, lineno
)
334 def clear_all_file_breaks(self
, filename
):
335 msg
= self
.call("clear_all_file_breaks", filename
)
338 def start_remote_debugger(rpcclt
, pyshell
):
339 """Start the subprocess debugger, initialize the debugger GUI and RPC link
341 Request the RPCServer start the Python subprocess debugger and link. Set
342 up the Idle side of the split debugger by instantiating the IdbProxy,
343 debugger GUI, and debugger GUIAdapter objects and linking them together.
345 Register the GUIAdapter with the RPCClient to handle debugger GUI
346 interaction requests coming from the subprocess debugger via the GUIProxy.
348 The IdbAdapter will pass execution and environment requests coming from the
349 Idle debugger GUI to the subprocess debugger via the IdbProxy.
354 idb_adap_oid
= rpcclt
.remotecall("exec", "start_the_debugger",\
356 idb_proxy
= IdbProxy(rpcclt
, pyshell
, idb_adap_oid
)
357 gui
= Debugger
.Debugger(pyshell
, idb_proxy
)
358 gui_adap
= GUIAdapter(rpcclt
, gui
)
359 rpcclt
.register(gui_adap_oid
, gui_adap
)
362 def close_remote_debugger(rpcclt
):
363 """Shut down subprocess debugger and Idle side of debugger RPC link
365 Request that the RPCServer shut down the subprocess debugger and link.
366 Unregister the GUIAdapter, which will cause a GC on the Idle process
367 debugger and RPC link objects. (The second reference to the debugger GUI
368 is deleted in PyShell.close_remote_debugger().)
371 close_subprocess_debugger(rpcclt
)
372 rpcclt
.unregister(gui_adap_oid
)
374 def close_subprocess_debugger(rpcclt
):
375 rpcclt
.remotecall("exec", "stop_the_debugger", (idb_adap_oid
,), {})
377 def restart_subprocess_debugger(rpcclt
):
378 idb_adap_oid_ret
= rpcclt
.remotecall("exec", "start_the_debugger",\
380 assert idb_adap_oid_ret
== idb_adap_oid
, 'Idb restarted with different oid'