0001 """Support for remote Python debugging. 0002 0003 Some ASCII art to describe the structure: 0004 0005 IN PYTHON SUBPROCESS # IN IDLE PROCESS 0006 # 0007 # oid='gui_adapter' 0008 +----------+ # +------------+ +-----+ 0009 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI | 0010 +-----+--calls-->+----------+ # +------------+ +-----+ 0011 | Idb | # / 0012 +-----+<-calls--+------------+ # +----------+<--calls-/ 0013 | IdbAdapter |<--remote#call--| IdbProxy | 0014 +------------+ # +----------+ 0015 oid='idb_adapter' # 0016 0017 The purpose of the Proxy and Adapter classes is to translate certain 0018 arguments and return values that cannot be transported through the RPC 0019 barrier, in particular frame and traceback objects. 0020 0021 """ 0022 0023 import sys 0024 import types 0025 import rpc 0026 import Debugger 0027 0028 debugging = 0 0029 0030 idb_adap_oid = "idb_adapter" 0031 gui_adap_oid = "gui_adapter" 0032 0033 #======================================= 0034 # 0035 # In the PYTHON subprocess: 0036 0037 frametable = {} 0038 dicttable = {} 0039 codetable = {} 0040 tracebacktable = {} 0041 0042 def wrap_frame(frame): 0043 fid = id(frame) 0044 frametable[fid] = frame 0045 return fid 0046 0047 def wrap_info(info): 0048 "replace info[2], a traceback instance, by its ID" 0049 if info is None: 0050 return None 0051 else: 0052 traceback = info[2] 0053 assert isinstance(traceback, types.TracebackType) 0054 traceback_id = id(traceback) 0055 tracebacktable[traceback_id] = traceback 0056 modified_info = (info[0], info[1], traceback_id) 0057 return modified_info 0058 0059 class GUIProxy: 0060 0061 def __init__(self, conn, gui_adap_oid): 0062 self.conn = conn 0063 self.oid = gui_adap_oid 0064 0065 def interaction(self, message, frame, info=None): 0066 # calls rpc.SocketIO.remotecall() via run.MyHandler instance 0067 # pass frame and traceback object IDs instead of the objects themselves 0068 self.conn.remotecall(self.oid, "interaction", 0069 (message, wrap_frame(frame), wrap_info(info)), 0070 {}) 0071 0072 class IdbAdapter: 0073 0074 def __init__(self, idb): 0075 self.idb = idb 0076 0077 #----------called by an IdbProxy---------- 0078 0079 def set_step(self): 0080 self.idb.set_step() 0081 0082 def set_quit(self): 0083 self.idb.set_quit() 0084 0085 def set_continue(self): 0086 self.idb.set_continue() 0087 0088 def set_next(self, fid): 0089 frame = frametable[fid] 0090 self.idb.set_next(frame) 0091 0092 def set_return(self, fid): 0093 frame = frametable[fid] 0094 self.idb.set_return(frame) 0095 0096 def get_stack(self, fid, tbid): 0097 ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid) 0098 frame = frametable[fid] 0099 if tbid is None: 0100 tb = None 0101 else: 0102 tb = tracebacktable[tbid] 0103 stack, i = self.idb.get_stack(frame, tb) 0104 ##print >>sys.__stderr__, "get_stack() ->", stack 0105 stack = [(wrap_frame(frame), k) for frame, k in stack] 0106 ##print >>sys.__stderr__, "get_stack() ->", stack 0107 return stack, i 0108 0109 def run(self, cmd): 0110 import __main__ 0111 self.idb.run(cmd, __main__.__dict__) 0112 0113 def set_break(self, filename, lineno): 0114 msg = self.idb.set_break(filename, lineno) 0115 return msg 0116 0117 def clear_break(self, filename, lineno): 0118 msg = self.idb.clear_break(filename, lineno) 0119 return msg 0120 0121 def clear_all_file_breaks(self, filename): 0122 msg = self.idb.clear_all_file_breaks(filename) 0123 return msg 0124 0125 #----------called by a FrameProxy---------- 0126 0127 def frame_attr(self, fid, name): 0128 frame = frametable[fid] 0129 return getattr(frame, name) 0130 0131 def frame_globals(self, fid): 0132 frame = frametable[fid] 0133 dict = frame.f_globals 0134 did = id(dict) 0135 dicttable[did] = dict 0136 return did 0137 0138 def frame_locals(self, fid): 0139 frame = frametable[fid] 0140 dict = frame.f_locals 0141 did = id(dict) 0142 dicttable[did] = dict 0143 return did 0144 0145 def frame_code(self, fid): 0146 frame = frametable[fid] 0147 code = frame.f_code 0148 cid = id(code) 0149 codetable[cid] = code 0150 return cid 0151 0152 #----------called by a CodeProxy---------- 0153 0154 def code_name(self, cid): 0155 code = codetable[cid] 0156 return code.co_name 0157 0158 def code_filename(self, cid): 0159 code = codetable[cid] 0160 return code.co_filename 0161 0162 #----------called by a DictProxy---------- 0163 0164 def dict_keys(self, did): 0165 dict = dicttable[did] 0166 return dict.keys() 0167 0168 def dict_item(self, did, key): 0169 dict = dicttable[did] 0170 value = dict[key] 0171 value = repr(value) 0172 return value 0173 0174 #----------end class IdbAdapter---------- 0175 0176 0177 def start_debugger(rpchandler, gui_adap_oid): 0178 """Start the debugger and its RPC link in the Python subprocess 0179 0180 Start the subprocess side of the split debugger and set up that side of the 0181 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter 0182 objects and linking them together. Register the IdbAdapter with the 0183 RPCServer to handle RPC requests from the split debugger GUI via the 0184 IdbProxy. 0185 0186 """ 0187 gui_proxy = GUIProxy(rpchandler, gui_adap_oid) 0188 idb = Debugger.Idb(gui_proxy) 0189 idb_adap = IdbAdapter(idb) 0190 rpchandler.register(idb_adap_oid, idb_adap) 0191 return idb_adap_oid 0192 0193 0194 #======================================= 0195 # 0196 # In the IDLE process: 0197 0198 0199 class FrameProxy: 0200 0201 def __init__(self, conn, fid): 0202 self._conn = conn 0203 self._fid = fid 0204 self._oid = "idb_adapter" 0205 self._dictcache = {} 0206 0207 def __getattr__(self, name): 0208 if name[:1] == "_": 0209 raise AttributeError, name 0210 if name == "f_code": 0211 return self._get_f_code() 0212 if name == "f_globals": 0213 return self._get_f_globals() 0214 if name == "f_locals": 0215 return self._get_f_locals() 0216 return self._conn.remotecall(self._oid, "frame_attr", 0217 (self._fid, name), {}) 0218 0219 def _get_f_code(self): 0220 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {}) 0221 return CodeProxy(self._conn, self._oid, cid) 0222 0223 def _get_f_globals(self): 0224 did = self._conn.remotecall(self._oid, "frame_globals", 0225 (self._fid,), {}) 0226 return self._get_dict_proxy(did) 0227 0228 def _get_f_locals(self): 0229 did = self._conn.remotecall(self._oid, "frame_locals", 0230 (self._fid,), {}) 0231 return self._get_dict_proxy(did) 0232 0233 def _get_dict_proxy(self, did): 0234 if self._dictcache.has_key(did): 0235 return self._dictcache[did] 0236 dp = DictProxy(self._conn, self._oid, did) 0237 self._dictcache[did] = dp 0238 return dp 0239 0240 0241 class CodeProxy: 0242 0243 def __init__(self, conn, oid, cid): 0244 self._conn = conn 0245 self._oid = oid 0246 self._cid = cid 0247 0248 def __getattr__(self, name): 0249 if name == "co_name": 0250 return self._conn.remotecall(self._oid, "code_name", 0251 (self._cid,), {}) 0252 if name == "co_filename": 0253 return self._conn.remotecall(self._oid, "code_filename", 0254 (self._cid,), {}) 0255 0256 0257 class DictProxy: 0258 0259 def __init__(self, conn, oid, did): 0260 self._conn = conn 0261 self._oid = oid 0262 self._did = did 0263 0264 def keys(self): 0265 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {}) 0266 0267 def __getitem__(self, key): 0268 return self._conn.remotecall(self._oid, "dict_item", 0269 (self._did, key), {}) 0270 0271 def __getattr__(self, name): 0272 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name 0273 raise AttributeError, name 0274 0275 0276 class GUIAdapter: 0277 0278 def __init__(self, conn, gui): 0279 self.conn = conn 0280 self.gui = gui 0281 0282 def interaction(self, message, fid, modified_info): 0283 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info) 0284 frame = FrameProxy(self.conn, fid) 0285 self.gui.interaction(message, frame, modified_info) 0286 0287 0288 class IdbProxy: 0289 0290 def __init__(self, conn, shell, oid): 0291 self.oid = oid 0292 self.conn = conn 0293 self.shell = shell 0294 0295 def call(self, methodname, *args, **kwargs): 0296 ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs) 0297 value = self.conn.remotecall(self.oid, methodname, args, kwargs) 0298 ##print "**IdbProxy.call %s returns %r" % (methodname, value) 0299 return value 0300 0301 def run(self, cmd, locals): 0302 # Ignores locals on purpose! 0303 seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {}) 0304 self.shell.interp.active_seq = seq 0305 0306 def get_stack(self, frame, tbid): 0307 # passing frame and traceback IDs, not the objects themselves 0308 stack, i = self.call("get_stack", frame._fid, tbid) 0309 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack] 0310 return stack, i 0311 0312 def set_continue(self): 0313 self.call("set_continue") 0314 0315 def set_step(self): 0316 self.call("set_step") 0317 0318 def set_next(self, frame): 0319 self.call("set_next", frame._fid) 0320 0321 def set_return(self, frame): 0322 self.call("set_return", frame._fid) 0323 0324 def set_quit(self): 0325 self.call("set_quit") 0326 0327 def set_break(self, filename, lineno): 0328 msg = self.call("set_break", filename, lineno) 0329 return msg 0330 0331 def clear_break(self, filename, lineno): 0332 msg = self.call("clear_break", filename, lineno) 0333 return msg 0334 0335 def clear_all_file_breaks(self, filename): 0336 msg = self.call("clear_all_file_breaks", filename) 0337 return msg 0338 0339 def start_remote_debugger(rpcclt, pyshell): 0340 """Start the subprocess debugger, initialize the debugger GUI and RPC link 0341 0342 Request the RPCServer start the Python subprocess debugger and link. Set 0343 up the Idle side of the split debugger by instantiating the IdbProxy, 0344 debugger GUI, and debugger GUIAdapter objects and linking them together. 0345 0346 Register the GUIAdapter with the RPCClient to handle debugger GUI 0347 interaction requests coming from the subprocess debugger via the GUIProxy. 0348 0349 The IdbAdapter will pass execution and environment requests coming from the 0350 Idle debugger GUI to the subprocess debugger via the IdbProxy. 0351 0352 """ 0353 global idb_adap_oid 0354 0355 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ 0356 (gui_adap_oid,), {}) 0357 idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid) 0358 gui = Debugger.Debugger(pyshell, idb_proxy) 0359 gui_adap = GUIAdapter(rpcclt, gui) 0360 rpcclt.register(gui_adap_oid, gui_adap) 0361 return gui 0362 0363 def close_remote_debugger(rpcclt): 0364 """Shut down subprocess debugger and Idle side of debugger RPC link 0365 0366 Request that the RPCServer shut down the subprocess debugger and link. 0367 Unregister the GUIAdapter, which will cause a GC on the Idle process 0368 debugger and RPC link objects. (The second reference to the debugger GUI 0369 is deleted in PyShell.close_remote_debugger().) 0370 0371 """ 0372 close_subprocess_debugger(rpcclt) 0373 rpcclt.unregister(gui_adap_oid) 0374 0375 def close_subprocess_debugger(rpcclt): 0376 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) 0377 0378 def restart_subprocess_debugger(rpcclt): 0379 idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ 0380 (gui_adap_oid,), {}) 0381 assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' 0382
Generated by PyXR 0.9.4