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 #=======================================
32 # In the PYTHON subprocess:
39 def wrap_frame(frame
):
41 frametable
[fid
] = frame
45 "replace info[2], a traceback instance, by its ID"
50 assert isinstance(traceback
, types
.TracebackType
)
51 traceback_id
= id(traceback
)
52 tracebacktable
[traceback_id
] = traceback
53 modified_info
= (info
[0], info
[1], traceback_id
)
58 def __init__(self
, conn
, gui_adap_oid
):
60 self
.oid
= gui_adap_oid
62 def interaction(self
, message
, frame
, info
=None):
63 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
64 # pass frame and traceback object IDs instead of the objects themselves
65 self
.conn
.remotecall(self
.oid
, "interaction",
66 (message
, wrap_frame(frame
), wrap_info(info
)),
71 def __init__(self
, idb
):
74 #----------called by an IdbProxy----------
82 def set_continue(self
):
83 self
.idb
.set_continue()
85 def set_next(self
, fid
):
86 frame
= frametable
[fid
]
87 self
.idb
.set_next(frame
)
89 def set_return(self
, fid
):
90 frame
= frametable
[fid
]
91 self
.idb
.set_return(frame
)
93 def get_stack(self
, fid
, tbid
):
94 ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
95 frame
= frametable
[fid
]
99 tb
= tracebacktable
[tbid
]
100 stack
, i
= self
.idb
.get_stack(frame
, tb
)
101 ##print >>sys.__stderr__, "get_stack() ->", stack
102 stack
= [(wrap_frame(frame
), k
) for frame
, k
in stack
]
103 ##print >>sys.__stderr__, "get_stack() ->", stack
108 self
.idb
.run(cmd
, __main__
.__dict
__)
110 def set_break(self
, filename
, lineno
):
111 msg
= self
.idb
.set_break(filename
, lineno
)
114 def clear_break(self
, filename
, lineno
):
115 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 dict = dicttable
[did
]
164 def dict_item(self
, did
, key
):
165 dict = dicttable
[did
]
170 #----------end class IdbAdapter----------
173 def start_debugger(rpchandler
, gui_adap_oid
):
174 """Start the debugger and its RPC link in the Python subprocess
176 Start the subprocess side of the split debugger and set up that side of the
177 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
178 objects and linking them together. Register the IdbAdapter with the
179 RPCServer to handle RPC requests from the split debugger GUI via the
183 gui_proxy
= GUIProxy(rpchandler
, gui_adap_oid
)
184 idb
= Debugger
.Idb(gui_proxy
)
185 idb_adap
= IdbAdapter(idb
)
186 idb_adap_oid
= "idb_adapter"
187 rpchandler
.register(idb_adap_oid
, idb_adap
)
191 #=======================================
193 # In the IDLE process:
198 def __init__(self
, conn
, fid
):
201 self
._oid
= "idb_adapter"
204 def __getattr__(self
, name
):
206 raise AttributeError, name
208 return self
._get
_f
_code
()
209 if name
== "f_globals":
210 return self
._get
_f
_globals
()
211 if name
== "f_locals":
212 return self
._get
_f
_locals
()
213 return self
._conn
.remotecall(self
._oid
, "frame_attr",
214 (self
._fid
, name
), {})
216 def _get_f_code(self
):
217 cid
= self
._conn
.remotecall(self
._oid
, "frame_code", (self
._fid
,), {})
218 return CodeProxy(self
._conn
, self
._oid
, cid
)
220 def _get_f_globals(self
):
221 did
= self
._conn
.remotecall(self
._oid
, "frame_globals",
223 return self
._get
_dict
_proxy
(did
)
225 def _get_f_locals(self
):
226 did
= self
._conn
.remotecall(self
._oid
, "frame_locals",
228 return self
._get
_dict
_proxy
(did
)
230 def _get_dict_proxy(self
, did
):
231 if self
._dictcache
.has_key(did
):
232 return self
._dictcache
[did
]
233 dp
= DictProxy(self
._conn
, self
._oid
, did
)
234 self
._dictcache
[did
] = dp
240 def __init__(self
, conn
, oid
, cid
):
245 def __getattr__(self
, name
):
246 if name
== "co_name":
247 return self
._conn
.remotecall(self
._oid
, "code_name",
249 if name
== "co_filename":
250 return self
._conn
.remotecall(self
._oid
, "code_filename",
256 def __init__(self
, conn
, oid
, did
):
262 return self
._conn
.remotecall(self
._oid
, "dict_keys", (self
._did
,), {})
264 def __getitem__(self
, key
):
265 return self
._conn
.remotecall(self
._oid
, "dict_item",
266 (self
._did
, key
), {})
268 def __getattr__(self
, name
):
269 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
270 raise AttributeError, name
275 def __init__(self
, conn
, gui
):
279 def interaction(self
, message
, fid
, modified_info
):
280 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
281 frame
= FrameProxy(self
.conn
, fid
)
282 self
.gui
.interaction(message
, frame
, modified_info
)
287 def __init__(self
, conn
, oid
):
291 def call(self
, methodname
, *args
, **kwargs
):
292 ##print "call %s %s %s" % (methodname, args, kwargs)
293 value
= self
.conn
.remotecall(self
.oid
, methodname
, args
, kwargs
)
294 ##print "return %s" % `value`
297 def run(self
, cmd
, locals):
298 # Ignores locals on purpose!
299 self
.call("run", cmd
)
301 def get_stack(self
, frame
, tbid
):
302 # passing frame and traceback IDs, not the objects themselves
303 stack
, i
= self
.call("get_stack", frame
._fid
, tbid
)
304 stack
= [(FrameProxy(self
.conn
, fid
), k
) for fid
, k
in stack
]
307 def set_continue(self
):
308 self
.call("set_continue")
311 self
.call("set_step")
313 def set_next(self
, frame
):
314 self
.call("set_next", frame
._fid
)
316 def set_return(self
, frame
):
317 self
.call("set_return", frame
._fid
)
320 self
.call("set_quit")
322 def set_break(self
, filename
, lineno
):
323 msg
= self
.call("set_break", filename
, lineno
)
326 def clear_break(self
, filename
, lineno
):
327 msg
= self
.call("clear_break", filename
, lineno
)
329 def clear_all_file_breaks(self
, filename
):
330 msg
= self
.call("clear_all_file_breaks", filename
)
333 def start_remote_debugger(rpcclt
, pyshell
):
334 """Start the subprocess debugger, initialize the debugger GUI and RPC link
336 Request the RPCServer start the Python subprocess debugger and link. Set
337 up the Idle side of the split debugger by instantiating the IdbProxy,
338 debugger GUI, and debugger GUIAdapter objects and linking them together.
340 Register the GUIAdapter with the RPCClient to handle debugger GUI
341 interaction requests coming from the subprocess debugger via the GUIProxy.
343 The IdbAdapter will pass execution and environment requests coming from the
344 Idle debugger GUI to the subprocess debugger via the IdbProxy.
347 gui_adap_oid
= "gui_adapter"
348 idb_adap_oid
= rpcclt
.remotecall("exec", "start_the_debugger",\
350 idb_proxy
= IdbProxy(rpcclt
, idb_adap_oid
)
351 gui
= Debugger
.Debugger(pyshell
, idb_proxy
)
352 gui_adap
= GUIAdapter(rpcclt
, gui
)
353 rpcclt
.register(gui_adap_oid
, gui_adap
)
356 def close_remote_debugger(rpcclt
):
357 """Shut down subprocess debugger and Idle side of debugger RPC link
359 Request that the RPCServer shut down the subprocess debugger and link.
360 Unregister the GUIAdapter, which will cause a GC on the Idle process
361 debugger and RPC link objects. (The second reference to the debugger GUI
362 is deleted in PyShell.close_remote_debugger().)
365 idb_adap_oid
= "idb_adapter"
366 rpcclt
.remotecall("exec", "stop_the_debugger", (idb_adap_oid
,), {})
367 gui_adap_oid
= "gui_adapter"
368 rpcclt
.unregister(gui_adap_oid
)