1 """Support for remote Python debugging. 2 3 Some 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 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. 20 21 """ 22 23 import types 24 from idlelib import rpc 25 from idlelib import Debugger 26 27 debugging = 0 28 29 idb_adap_oid = "idb_adapter" 30 gui_adap_oid = "gui_adapter" 31 32 #======================================= 33 # 34 # In the PYTHON subprocess: 35 36 frametable = {} 37 dicttable = {} 38 codetable = {} 39 tracebacktable = {} 40 41 def wrap_frame(frame): 42 fid = id(frame) 43 frametable[fid] = frame 44 return fid 45 46 def wrap_info(info): 47 "replace info[2], a traceback instance, by its ID" 48 if info is None: 49 return None 50 else: 51 traceback = info[2] 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) 56 return modified_info 57 58 class GUIProxy: 59 60 def __init__(self, conn, gui_adap_oid): 61 self.conn = conn 62 self.oid = gui_adap_oid 63 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)), 69 {}) 70 71 class IdbAdapter: 72 73 def __init__(self, idb): 74 self.idb = idb 75 76 #----------called by an IdbProxy---------- 77 78 def set_step(self): 79 self.idb.set_step() 80 81 def set_quit(self): 82 self.idb.set_quit() 83 84 def set_continue(self): 85 self.idb.set_continue() 86 87 def set_next(self, fid): 88 frame = frametable[fid] 89 self.idb.set_next(frame) 90 91 def set_return(self, fid): 92 frame = frametable[fid] 93 self.idb.set_return(frame) 94 95 def get_stack(self, fid, tbid): 96 ##print >>sys.__stderr__, "get_stack(%r, %r)" % (fid, tbid) 97 frame = frametable[fid] 98 if tbid is None: 99 tb = None 100 else: 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 106 return stack, i 107 108 def run(self, cmd): 109 import __main__ 110 self.idb.run(cmd, __main__.__dict__) 111 112 def set_break(self, filename, lineno): 113 msg = self.idb.set_break(filename, lineno) 114 return msg 115 116 def clear_break(self, filename, lineno): 117 msg = self.idb.clear_break(filename, lineno) 118 return msg 119 120 def clear_all_file_breaks(self, filename): 121 msg = self.idb.clear_all_file_breaks(filename) 122 return msg 123 124 #----------called by a FrameProxy---------- 125 126 def frame_attr(self, fid, name): 127 frame = frametable[fid] 128 return getattr(frame, name) 129 130 def frame_globals(self, fid): 131 frame = frametable[fid] 132 dict = frame.f_globals 133 did = id(dict) 134 dicttable[did] = dict 135 return did 136 137 def frame_locals(self, fid): 138 frame = frametable[fid] 139 dict = frame.f_locals 140 did = id(dict) 141 dicttable[did] = dict 142 return did 143 144 def frame_code(self, fid): 145 frame = frametable[fid] 146 code = frame.f_code 147 cid = id(code) 148 codetable[cid] = code 149 return cid 150 151 #----------called by a CodeProxy---------- 152 153 def code_name(self, cid): 154 code = codetable[cid] 155 return code.co_name 156 157 def code_filename(self, cid): 158 code = codetable[cid] 159 return code.co_filename 160 161 #----------called by a DictProxy---------- 162 163 def dict_keys(self, did): 164 dict = dicttable[did] 165 return dict.keys() 166 167 def dict_item(self, did, key): 168 dict = dicttable[did] 169 value = dict[key] 170 value = repr(value) 171 return value 172 173 #----------end class IdbAdapter---------- 174 175 176 def start_debugger(rpchandler, gui_adap_oid): 177 """Start the debugger and its RPC link in the Python subprocess 178 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 183 IdbProxy. 184 185 """ 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) 190 return idb_adap_oid 191 192 193 #======================================= 194 # 195 # In the IDLE process: 196 197 198 class FrameProxy: 199 200 def __init__(self, conn, fid): 201 self._conn = conn 202 self._fid = fid 203 self._oid = "idb_adapter" 204 self._dictcache = {} 205 206 def __getattr__(self, name): 207 if name[:1] == "_": 208 raise AttributeError, name 209 if name == "f_code": 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), {}) 217 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) 221 222 def _get_f_globals(self): 223 did = self._conn.remotecall(self._oid, "frame_globals", 224 (self._fid,), {}) 225 return self._get_dict_proxy(did) 226 227 def _get_f_locals(self): 228 did = self._conn.remotecall(self._oid, "frame_locals", 229 (self._fid,), {}) 230 return self._get_dict_proxy(did) 231 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 237 return dp 238 239 240 class CodeProxy: 241 242 def __init__(self, conn, oid, cid): 243 self._conn = conn 244 self._oid = oid 245 self._cid = cid 246 247 def __getattr__(self, name): 248 if name == "co_name": 249 return self._conn.remotecall(self._oid, "code_name", 250 (self._cid,), {}) 251 if name == "co_filename": 252 return self._conn.remotecall(self._oid, "code_filename", 253 (self._cid,), {}) 254 255 256 class DictProxy: 257 258 def __init__(self, conn, oid, did): 259 self._conn = conn 260 self._oid = oid 261 self._did = did 262 263 def keys(self): 264 return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {}) 265 266 def __getitem__(self, key): 267 return self._conn.remotecall(self._oid, "dict_item", 268 (self._did, key), {}) 269 270 def __getattr__(self, name): 271 ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name 272 raise AttributeError, name 273 274 275 class GUIAdapter: 276 277 def __init__(self, conn, gui): 278 self.conn = conn 279 self.gui = gui 280 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) 285 286 287 class IdbProxy: 288 289 def __init__(self, conn, shell, oid): 290 self.oid = oid 291 self.conn = conn 292 self.shell = shell 293 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) 298 return value 299 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 304 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] 309 return stack, i 310 311 def set_continue(self): 312 self.call("set_continue") 313 314 def set_step(self): 315 self.call("set_step") 316 317 def set_next(self, frame): 318 self.call("set_next", frame._fid) 319 320 def set_return(self, frame): 321 self.call("set_return", frame._fid) 322 323 def set_quit(self): 324 self.call("set_quit") 325 326 def set_break(self, filename, lineno): 327 msg = self.call("set_break", filename, lineno) 328 return msg 329 330 def clear_break(self, filename, lineno): 331 msg = self.call("clear_break", filename, lineno) 332 return msg 333 334 def clear_all_file_breaks(self, filename): 335 msg = self.call("clear_all_file_breaks", filename) 336 return msg 337 338 def start_remote_debugger(rpcclt, pyshell): 339 """Start the subprocess debugger, initialize the debugger GUI and RPC link 340 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. 344 345 Register the GUIAdapter with the RPCClient to handle debugger GUI 346 interaction requests coming from the subprocess debugger via the GUIProxy. 347 348 The IdbAdapter will pass execution and environment requests coming from the 349 Idle debugger GUI to the subprocess debugger via the IdbProxy. 350 351 """ 352 global idb_adap_oid 353 354 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ 355 (gui_adap_oid,), {}) 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) 360 return gui 361 362 def close_remote_debugger(rpcclt): 363 """Shut down subprocess debugger and Idle side of debugger RPC link 364 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().) 369 370 """ 371 close_subprocess_debugger(rpcclt) 372 rpcclt.unregister(gui_adap_oid) 373 374 def close_subprocess_debugger(rpcclt): 375 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) 376 377 def restart_subprocess_debugger(rpcclt): 378 idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ 379 (gui_adap_oid,), {}) 380 assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' 381