1"""Support for remote Python debugging. 2 3Some ASCII art to describe the structure: 4 5 IN PYTHON SUBPROCESS # IN IDLE PROCESS 6 # 7 # oid='gui_adapter' 8 +----------+ # +------------+ +-----+ 9 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI | 10+-----+--calls-->+----------+ # +------------+ +-----+ 11| Idb | # / 12+-----+<-calls--+------------+ # +----------+<--calls-/ 13 | IdbAdapter |<--remote#call--| IdbProxy | 14 +------------+ # +----------+ 15 oid='idb_adapter' # 16 17The purpose of the Proxy and Adapter classes is to translate certain 18arguments and return values that cannot be transported through the RPC 19barrier, in particular frame and traceback objects. 20 21""" 22 23import types 24from idlelib import Debugger 25 26debugging = 0 27 28idb_adap_oid = "idb_adapter" 29gui_adap_oid = "gui_adapter" 30 31#======================================= 32# 33# In the PYTHON subprocess: 34 35frametable = {} 36dicttable = {} 37codetable = {} 38tracebacktable = {} 39 40def wrap_frame(frame): 41 fid = id(frame) 42 frametable[fid] = frame 43 return fid 44 45def wrap_info(info): 46 "replace info[2], a traceback instance, by its ID" 47 if info is None: 48 return None 49 else: 50 traceback = info[2] 51 assert isinstance(traceback, types.TracebackType) 52 traceback_id = id(traceback) 53 tracebacktable[traceback_id] = traceback 54 modified_info = (info[0], info[1], traceback_id) 55 return modified_info 56 57class GUIProxy: 58 59 def __init__(self, conn, gui_adap_oid): 60 self.conn = conn 61 self.oid = gui_adap_oid 62 63 def interaction(self, message, frame, info=None): 64 # calls rpc.SocketIO.remotecall() via run.MyHandler instance 65 # pass frame and traceback object IDs instead of the objects themselves 66 self.conn.remotecall(self.oid, "interaction", 67 (message, wrap_frame(frame), wrap_info(info)), 68 {}) 69 70class IdbAdapter: 71 72 def __init__(self, idb): 73 self.idb = idb 74 75 #----------called by an IdbProxy---------- 76 77 def set_step(self): 78 self.idb.set_step() 79 80 def set_quit(self): 81 self.idb.set_quit() 82 83 def set_continue(self): 84 self.idb.set_continue() 85 86 def set_next(self, fid): 87 frame = frametable[fid] 88 self.idb.set_next(frame) 89 90 def set_return(self, fid): 91 frame = frametable[fid] 92 self.idb.set_return(frame) 93 94 def get_stack(self, fid, tbid): 95 ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid) 96 frame = frametable[fid] 97 if tbid is None: 98 tb = None 99 else: 100 tb = tracebacktable[tbid] 101 stack, i = self.idb.get_stack(frame, tb) 102 ##print >>sys.__stderr__, "get_stack() ->", stack 103 stack = [(wrap_frame(frame2), k) for frame2, k in stack] 104 ##print >>sys.__stderr__, "get_stack() ->", stack 105 return stack, i 106 107 def run(self, cmd): 108 import __main__ 109 self.idb.run(cmd, __main__.__dict__) 110 111 def set_break(self, filename, lineno): 112 msg = self.idb.set_break(filename, lineno) 113 return msg 114 115 def clear_break(self, filename, lineno): 116 msg = self.idb.clear_break(filename, lineno) 117 return msg 118 119 def clear_all_file_breaks(self, filename): 120 msg = self.idb.clear_all_file_breaks(filename) 121 return msg 122 123 #----------called by a FrameProxy---------- 124 125 def frame_attr(self, fid, name): 126 frame = frametable[fid] 127 return getattr(frame, name) 128 129 def frame_globals(self, fid): 130 frame = frametable[fid] 131 dict = frame.f_globals 132 did = id(dict) 133 dicttable[did] = dict 134 return did 135 136 def frame_locals(self, fid): 137 frame = frametable[fid] 138 dict = frame.f_locals 139 did = id(dict) 140 dicttable[did] = dict 141 return did 142 143 def frame_code(self, fid): 144 frame = frametable[fid] 145 code = frame.f_code 146 cid = id(code) 147 codetable[cid] = code 148 return cid 149 150 #----------called by a CodeProxy---------- 151 152 def code_name(self, cid): 153 code = codetable[cid] 154 return code.co_name 155 156 def code_filename(self, cid): 157 code = codetable[cid] 158 return code.co_filename 159 160 #----------called by a DictProxy---------- 161 162 def dict_keys(self, did): 163 dict = dicttable[did] 164 return dict.keys() 165 166 def dict_item(self, did, key): 167 dict = dicttable[did] 168 value = dict[key] 169 value = repr(value) 170 return value 171 172#----------end class IdbAdapter---------- 173 174 175def start_debugger(rpchandler, gui_adap_oid): 176 """Start the debugger and its RPC link in the Python subprocess 177 178 Start the subprocess side of the split debugger and set up that side of the 179 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter 180 objects and linking them together. Register the IdbAdapter with the 181 RPCServer to handle RPC requests from the split debugger GUI via the 182 IdbProxy. 183 184 """ 185 gui_proxy = GUIProxy(rpchandler, gui_adap_oid) 186 idb = Debugger.Idb(gui_proxy) 187 idb_adap = IdbAdapter(idb) 188 rpchandler.register(idb_adap_oid, idb_adap) 189 return idb_adap_oid 190 191 192#======================================= 193# 194# In the IDLE process: 195 196 197class FrameProxy: 198 199 def __init__(self, conn, fid): 200 self._conn = conn 201 self._fid = fid 202 self._oid = "idb_adapter" 203 self._dictcache = {} 204 205 def __getattr__(self, name): 206 if name[:1] == "_": 207 raise AttributeError, name 208 if name == "f_code": 209 return self._get_f_code() 210 if name == "f_globals": 211 return self._get_f_globals() 212 if name == "f_locals": 213 return self._get_f_locals() 214 return self._conn.remotecall(self._oid, "frame_attr", 215 (self._fid, name), {}) 216 217 def _get_f_code(self): 218 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {}) 219 return CodeProxy(self._conn, self._oid, cid) 220 221 def _get_f_globals(self): 222 did = self._conn.remotecall(self._oid, "frame_globals", 223 (self._fid,), {}) 224 return self._get_dict_proxy(did) 225 226 def _get_f_locals(self): 227 did = self._conn.remotecall(self._oid, "frame_locals", 228 (self._fid,), {}) 229 return self._get_dict_proxy(did) 230 231 def _get_dict_proxy(self, did): 232 if did in self._dictcache: 233 return self._dictcache[did] 234 dp = DictProxy(self._conn, self._oid, did) 235 self._dictcache[did] = dp 236 return dp 237 238 239class CodeProxy: 240 241 def __init__(self, conn, oid, cid): 242 self._conn = conn 243 self._oid = oid 244 self._cid = cid 245 246 def __getattr__(self, name): 247 if name == "co_name": 248 return self._conn.remotecall(self._oid, "code_name", 249 (self._cid,), {}) 250 if name == "co_filename": 251 return self._conn.remotecall(self._oid, "code_filename", 252 (self._cid,), {}) 253 254 255class DictProxy: 256 257 def __init__(self, conn, oid, did): 258 self._conn = conn 259 self._oid = oid 260 self._did = did 261 262 def keys(self): 263 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {}) 264 265 def __getitem__(self, key): 266 return self._conn.remotecall(self._oid, "dict_item", 267 (self._did, key), {}) 268 269 def __getattr__(self, name): 270 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name 271 raise AttributeError, name 272 273 274class GUIAdapter: 275 276 def __init__(self, conn, gui): 277 self.conn = conn 278 self.gui = gui 279 280 def interaction(self, message, fid, modified_info): 281 ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info) 282 frame = FrameProxy(self.conn, fid) 283 self.gui.interaction(message, frame, modified_info) 284 285 286class IdbProxy: 287 288 def __init__(self, conn, shell, oid): 289 self.oid = oid 290 self.conn = conn 291 self.shell = shell 292 293 def call(self, methodname, *args, **kwargs): 294 ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs) 295 value = self.conn.remotecall(self.oid, methodname, args, kwargs) 296 ##print "**IdbProxy.call %s returns %r" % (methodname, value) 297 return value 298 299 def run(self, cmd, locals): 300 # Ignores locals on purpose! 301 seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {}) 302 self.shell.interp.active_seq = seq 303 304 def get_stack(self, frame, tbid): 305 # passing frame and traceback IDs, not the objects themselves 306 stack, i = self.call("get_stack", frame._fid, tbid) 307 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack] 308 return stack, i 309 310 def set_continue(self): 311 self.call("set_continue") 312 313 def set_step(self): 314 self.call("set_step") 315 316 def set_next(self, frame): 317 self.call("set_next", frame._fid) 318 319 def set_return(self, frame): 320 self.call("set_return", frame._fid) 321 322 def set_quit(self): 323 self.call("set_quit") 324 325 def set_break(self, filename, lineno): 326 msg = self.call("set_break", filename, lineno) 327 return msg 328 329 def clear_break(self, filename, lineno): 330 msg = self.call("clear_break", filename, lineno) 331 return msg 332 333 def clear_all_file_breaks(self, filename): 334 msg = self.call("clear_all_file_breaks", filename) 335 return msg 336 337def start_remote_debugger(rpcclt, pyshell): 338 """Start the subprocess debugger, initialize the debugger GUI and RPC link 339 340 Request the RPCServer start the Python subprocess debugger and link. Set 341 up the Idle side of the split debugger by instantiating the IdbProxy, 342 debugger GUI, and debugger GUIAdapter objects and linking them together. 343 344 Register the GUIAdapter with the RPCClient to handle debugger GUI 345 interaction requests coming from the subprocess debugger via the GUIProxy. 346 347 The IdbAdapter will pass execution and environment requests coming from the 348 Idle debugger GUI to the subprocess debugger via the IdbProxy. 349 350 """ 351 global idb_adap_oid 352 353 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ 354 (gui_adap_oid,), {}) 355 idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid) 356 gui = Debugger.Debugger(pyshell, idb_proxy) 357 gui_adap = GUIAdapter(rpcclt, gui) 358 rpcclt.register(gui_adap_oid, gui_adap) 359 return gui 360 361def close_remote_debugger(rpcclt): 362 """Shut down subprocess debugger and Idle side of debugger RPC link 363 364 Request that the RPCServer shut down the subprocess debugger and link. 365 Unregister the GUIAdapter, which will cause a GC on the Idle process 366 debugger and RPC link objects. (The second reference to the debugger GUI 367 is deleted in PyShell.close_remote_debugger().) 368 369 """ 370 close_subprocess_debugger(rpcclt) 371 rpcclt.unregister(gui_adap_oid) 372 373def close_subprocess_debugger(rpcclt): 374 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) 375 376def restart_subprocess_debugger(rpcclt): 377 idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ 378 (gui_adap_oid,), {}) 379 assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' 380