~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/idlelib/RemoteDebugger.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        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
        stack = [(wrap_frame(frame), k) for frame, k in stack]
 
103
        return stack, i
 
104
 
 
105
    def run(self, cmd):
 
106
        import __main__
 
107
        self.idb.run(cmd, __main__.__dict__)
 
108
 
 
109
    def set_break(self, filename, lineno):
 
110
        msg = self.idb.set_break(filename, lineno)
 
111
        return msg
 
112
 
 
113
    def clear_break(self, filename, lineno):
 
114
        msg = self.idb.clear_break(filename, lineno)
 
115
        return msg
 
116
 
 
117
    def clear_all_file_breaks(self, filename):
 
118
        msg = self.idb.clear_all_file_breaks(filename)
 
119
        return msg
 
120
 
 
121
    #----------called by a FrameProxy----------
 
122
 
 
123
    def frame_attr(self, fid, name):
 
124
        frame = frametable[fid]
 
125
        return getattr(frame, name)
 
126
 
 
127
    def frame_globals(self, fid):
 
128
        frame = frametable[fid]
 
129
        dict = frame.f_globals
 
130
        did = id(dict)
 
131
        dicttable[did] = dict
 
132
        return did
 
133
 
 
134
    def frame_locals(self, fid):
 
135
        frame = frametable[fid]
 
136
        dict = frame.f_locals
 
137
        did = id(dict)
 
138
        dicttable[did] = dict
 
139
        return did
 
140
 
 
141
    def frame_code(self, fid):
 
142
        frame = frametable[fid]
 
143
        code = frame.f_code
 
144
        cid = id(code)
 
145
        codetable[cid] = code
 
146
        return cid
 
147
 
 
148
    #----------called by a CodeProxy----------
 
149
 
 
150
    def code_name(self, cid):
 
151
        code = codetable[cid]
 
152
        return code.co_name
 
153
 
 
154
    def code_filename(self, cid):
 
155
        code = codetable[cid]
 
156
        return code.co_filename
 
157
 
 
158
    #----------called by a DictProxy----------
 
159
 
 
160
    def dict_keys(self, did):
 
161
        raise NotImplemented("dict_keys not public or pickleable")
 
162
##         dict = dicttable[did]
 
163
##         return dict.keys()
 
164
 
 
165
    ### Needed until dict_keys is type is finished and pickealable.
 
166
    ### Will probably need to extend rpc.py:SocketIO._proxify at that time.
 
167
    def dict_keys_list(self, did):
 
168
        dict = dicttable[did]
 
169
        return list(dict.keys())
 
170
 
 
171
    def dict_item(self, did, key):
 
172
        dict = dicttable[did]
 
173
        value = dict[key]
 
174
        value = repr(value) ### can't pickle module 'builtins'
 
175
        return value
 
176
 
 
177
#----------end class IdbAdapter----------
 
178
 
 
179
 
 
180
def start_debugger(rpchandler, gui_adap_oid):
 
181
    """Start the debugger and its RPC link in the Python subprocess
 
182
 
 
183
    Start the subprocess side of the split debugger and set up that side of the
 
184
    RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
 
185
    objects and linking them together.  Register the IdbAdapter with the
 
186
    RPCServer to handle RPC requests from the split debugger GUI via the
 
187
    IdbProxy.
 
188
 
 
189
    """
 
190
    gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
 
191
    idb = Debugger.Idb(gui_proxy)
 
192
    idb_adap = IdbAdapter(idb)
 
193
    rpchandler.register(idb_adap_oid, idb_adap)
 
194
    return idb_adap_oid
 
195
 
 
196
 
 
197
#=======================================
 
198
#
 
199
# In the IDLE process:
 
200
 
 
201
 
 
202
class FrameProxy:
 
203
 
 
204
    def __init__(self, conn, fid):
 
205
        self._conn = conn
 
206
        self._fid = fid
 
207
        self._oid = "idb_adapter"
 
208
        self._dictcache = {}
 
209
 
 
210
    def __getattr__(self, name):
 
211
        if name[:1] == "_":
 
212
            raise AttributeError(name)
 
213
        if name == "f_code":
 
214
            return self._get_f_code()
 
215
        if name == "f_globals":
 
216
            return self._get_f_globals()
 
217
        if name == "f_locals":
 
218
            return self._get_f_locals()
 
219
        return self._conn.remotecall(self._oid, "frame_attr",
 
220
                                     (self._fid, name), {})
 
221
 
 
222
    def _get_f_code(self):
 
223
        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
 
224
        return CodeProxy(self._conn, self._oid, cid)
 
225
 
 
226
    def _get_f_globals(self):
 
227
        did = self._conn.remotecall(self._oid, "frame_globals",
 
228
                                    (self._fid,), {})
 
229
        return self._get_dict_proxy(did)
 
230
 
 
231
    def _get_f_locals(self):
 
232
        did = self._conn.remotecall(self._oid, "frame_locals",
 
233
                                    (self._fid,), {})
 
234
        return self._get_dict_proxy(did)
 
235
 
 
236
    def _get_dict_proxy(self, did):
 
237
        if did in self._dictcache:
 
238
            return self._dictcache[did]
 
239
        dp = DictProxy(self._conn, self._oid, did)
 
240
        self._dictcache[did] = dp
 
241
        return dp
 
242
 
 
243
 
 
244
class CodeProxy:
 
245
 
 
246
    def __init__(self, conn, oid, cid):
 
247
        self._conn = conn
 
248
        self._oid = oid
 
249
        self._cid = cid
 
250
 
 
251
    def __getattr__(self, name):
 
252
        if name == "co_name":
 
253
            return self._conn.remotecall(self._oid, "code_name",
 
254
                                         (self._cid,), {})
 
255
        if name == "co_filename":
 
256
            return self._conn.remotecall(self._oid, "code_filename",
 
257
                                         (self._cid,), {})
 
258
 
 
259
 
 
260
class DictProxy:
 
261
 
 
262
    def __init__(self, conn, oid, did):
 
263
        self._conn = conn
 
264
        self._oid = oid
 
265
        self._did = did
 
266
 
 
267
##    def keys(self):
 
268
##        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
 
269
 
 
270
    # 'temporary' until dict_keys is a pickleable built-in type
 
271
    def keys(self):
 
272
        return self._conn.remotecall(self._oid,
 
273
                                     "dict_keys_list", (self._did,), {})
 
274
 
 
275
    def __getitem__(self, key):
 
276
        return self._conn.remotecall(self._oid, "dict_item",
 
277
                                     (self._did, key), {})
 
278
 
 
279
    def __getattr__(self, name):
 
280
        ##print("*** Failed DictProxy.__getattr__:", name)
 
281
        raise AttributeError(name)
 
282
 
 
283
 
 
284
class GUIAdapter:
 
285
 
 
286
    def __init__(self, conn, gui):
 
287
        self.conn = conn
 
288
        self.gui = gui
 
289
 
 
290
    def interaction(self, message, fid, modified_info):
 
291
        ##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info))
 
292
        frame = FrameProxy(self.conn, fid)
 
293
        self.gui.interaction(message, frame, modified_info)
 
294
 
 
295
 
 
296
class IdbProxy:
 
297
 
 
298
    def __init__(self, conn, shell, oid):
 
299
        self.oid = oid
 
300
        self.conn = conn
 
301
        self.shell = shell
 
302
 
 
303
    def call(self, methodname, *args, **kwargs):
 
304
        ##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs))
 
305
        value = self.conn.remotecall(self.oid, methodname, args, kwargs)
 
306
        ##print("*** IdbProxy.call %s returns %r" % (methodname, value))
 
307
        return value
 
308
 
 
309
    def run(self, cmd, locals):
 
310
        # Ignores locals on purpose!
 
311
        seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
 
312
        self.shell.interp.active_seq = seq
 
313
 
 
314
    def get_stack(self, frame, tbid):
 
315
        # passing frame and traceback IDs, not the objects themselves
 
316
        stack, i = self.call("get_stack", frame._fid, tbid)
 
317
        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
 
318
        return stack, i
 
319
 
 
320
    def set_continue(self):
 
321
        self.call("set_continue")
 
322
 
 
323
    def set_step(self):
 
324
        self.call("set_step")
 
325
 
 
326
    def set_next(self, frame):
 
327
        self.call("set_next", frame._fid)
 
328
 
 
329
    def set_return(self, frame):
 
330
        self.call("set_return", frame._fid)
 
331
 
 
332
    def set_quit(self):
 
333
        self.call("set_quit")
 
334
 
 
335
    def set_break(self, filename, lineno):
 
336
        msg = self.call("set_break", filename, lineno)
 
337
        return msg
 
338
 
 
339
    def clear_break(self, filename, lineno):
 
340
        msg = self.call("clear_break", filename, lineno)
 
341
        return msg
 
342
 
 
343
    def clear_all_file_breaks(self, filename):
 
344
        msg = self.call("clear_all_file_breaks", filename)
 
345
        return msg
 
346
 
 
347
def start_remote_debugger(rpcclt, pyshell):
 
348
    """Start the subprocess debugger, initialize the debugger GUI and RPC link
 
349
 
 
350
    Request the RPCServer start the Python subprocess debugger and link.  Set
 
351
    up the Idle side of the split debugger by instantiating the IdbProxy,
 
352
    debugger GUI, and debugger GUIAdapter objects and linking them together.
 
353
 
 
354
    Register the GUIAdapter with the RPCClient to handle debugger GUI
 
355
    interaction requests coming from the subprocess debugger via the GUIProxy.
 
356
 
 
357
    The IdbAdapter will pass execution and environment requests coming from the
 
358
    Idle debugger GUI to the subprocess debugger via the IdbProxy.
 
359
 
 
360
    """
 
361
    global idb_adap_oid
 
362
 
 
363
    idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
 
364
                                   (gui_adap_oid,), {})
 
365
    idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
 
366
    gui = Debugger.Debugger(pyshell, idb_proxy)
 
367
    gui_adap = GUIAdapter(rpcclt, gui)
 
368
    rpcclt.register(gui_adap_oid, gui_adap)
 
369
    return gui
 
370
 
 
371
def close_remote_debugger(rpcclt):
 
372
    """Shut down subprocess debugger and Idle side of debugger RPC link
 
373
 
 
374
    Request that the RPCServer shut down the subprocess debugger and link.
 
375
    Unregister the GUIAdapter, which will cause a GC on the Idle process
 
376
    debugger and RPC link objects.  (The second reference to the debugger GUI
 
377
    is deleted in PyShell.close_remote_debugger().)
 
378
 
 
379
    """
 
380
    close_subprocess_debugger(rpcclt)
 
381
    rpcclt.unregister(gui_adap_oid)
 
382
 
 
383
def close_subprocess_debugger(rpcclt):
 
384
    rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
 
385
 
 
386
def restart_subprocess_debugger(rpcclt):
 
387
    idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
 
388
                                         (gui_adap_oid,), {})
 
389
    assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'