DebugClients/Python/DebugBase.py

changeset 0
de9c2efb9d02
child 13
1af94a91f439
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the debug base class.
8 """
9
10 import sys
11 import traceback
12 import bdb
13 import os
14 import types
15 import atexit
16 import inspect
17
18 from DebugProtocol import *
19
20 gRecursionLimit = 64
21
22 def printerr(s):
23 """
24 Module function used for debugging the debug client.
25
26 @param s data to be printed
27 """
28 import sys
29 sys.__stderr__.write('%s\n' % unicode(s))
30 sys.__stderr__.flush()
31
32 def setRecursionLimit(limit):
33 """
34 Module function to set the recursion limit.
35
36 @param limit recursion limit (integer)
37 """
38 global gRecursionLimit
39 gRecursionLimit = limit
40
41 class DebugBase(bdb.Bdb):
42 """
43 Class implementing base class of the debugger.
44
45 Provides simple wrapper methods around bdb for the 'owning' client to
46 call to step etc.
47 """
48 def __init__(self, dbgClient):
49 """
50 Constructor
51
52 @param dbgClient the owning client
53 """
54 bdb.Bdb.__init__(self)
55
56 self._dbgClient = dbgClient
57 self._mainThread = 1
58
59 self.breaks = self._dbgClient.breakpoints
60
61 self.__event = ""
62 self.__isBroken = ""
63 self.cFrame = None
64
65 # current frame we are at
66 self.currentFrame = None
67
68 # frame that we are stepping in, can be different than currentFrame
69 self.stepFrame = None
70
71 # provide a hook to perform a hard breakpoint
72 # Use it like this:
73 # if hasattr(sys, 'breakpoint): sys.breakpoint()
74 sys.breakpoint = self.set_trace
75
76 # initialize parent
77 bdb.Bdb.reset(self)
78
79 self.__recursionDepth = -1
80 self.setRecursionDepth(inspect.currentframe())
81
82 def getCurrentFrame(self):
83 """
84 Public method to return the current frame.
85
86 @return the current frame
87 """
88 return self.currentFrame
89
90 def step(self, traceMode):
91 """
92 Public method to perform a step operation in this thread.
93
94 @param traceMode If it is non-zero, then the step is a step into,
95 otherwise it is a step over.
96 """
97 self.stepFrame = self.currentFrame
98
99 if traceMode:
100 self.currentFrame = None
101 self.set_step()
102 else:
103 self.set_next(self.currentFrame)
104
105 def stepOut(self):
106 """
107 Public method to perform a step out of the current call.
108 """
109 self.stepFrame = self.currentFrame
110 self.set_return(self.currentFrame)
111
112 def go(self, special):
113 """
114 Public method to resume the thread.
115
116 It resumes the thread stopping only at breakpoints or exceptions.
117
118 @param special flag indicating a special continue operation
119 """
120 self.currentFrame = None
121 self.set_continue(special)
122
123 def setRecursionDepth(self, frame):
124 """
125 Public method to determine the current recursion depth.
126
127 @param frame The current stack frame.
128 """
129 self.__recursionDepth = 0
130 while frame is not None:
131 self.__recursionDepth += 1
132 frame = frame.f_back
133
134 def profile(self, frame, event, arg):
135 """
136 Public method used to trace some stuff independant of the debugger
137 trace function.
138
139 @param frame The current stack frame.
140 @param event The trace event (string)
141 @param arg The arguments
142 """
143 if event == 'return':
144 self.cFrame = frame.f_back
145 self.__recursionDepth -= 1
146 elif event == 'call':
147 self.cFrame = frame
148 self.__recursionDepth += 1
149 if self.__recursionDepth > gRecursionLimit:
150 raise RuntimeError('maximum recursion depth exceeded\n'
151 '(offending frame is two down the stack)')
152
153 def trace_dispatch(self, frame, event, arg):
154 """
155 Reimplemented from bdb.py to do some special things.
156
157 This specialty is to check the connection to the debug server
158 for new events (i.e. new breakpoints) while we are going through
159 the code.
160
161 @param frame The current stack frame.
162 @param event The trace event (string)
163 @param arg The arguments
164 @return local trace function
165 """
166 if self.quitting:
167 return # None
168
169 # give the client a chance to push through new break points.
170 self._dbgClient.eventPoll()
171
172 self.__event == event
173 self.__isBroken = False
174
175 if event == 'line':
176 return self.dispatch_line(frame)
177 if event == 'call':
178 return self.dispatch_call(frame, arg)
179 if event == 'return':
180 return self.dispatch_return(frame, arg)
181 if event == 'exception':
182 return self.dispatch_exception(frame, arg)
183 if event == 'c_call':
184 return self.trace_dispatch
185 if event == 'c_exception':
186 return self.trace_dispatch
187 if event == 'c_return':
188 return self.trace_dispatch
189 print 'DebugBase.trace_dispatch: unknown debugging event:', `event`
190 return self.trace_dispatch
191
192 def dispatch_line(self, frame):
193 """
194 Reimplemented from bdb.py to do some special things.
195
196 This speciality is to check the connection to the debug server
197 for new events (i.e. new breakpoints) while we are going through
198 the code.
199
200 @param frame The current stack frame.
201 @return local trace function
202 """
203 if self.stop_here(frame) or self.break_here(frame):
204 self.user_line(frame)
205 if self.quitting: raise bdb.BdbQuit
206 return self.trace_dispatch
207
208 def dispatch_return(self, frame, arg):
209 """
210 Reimplemented from bdb.py to handle passive mode cleanly.
211
212 @param frame The current stack frame.
213 @param arg The arguments
214 @return local trace function
215 """
216 if self.stop_here(frame) or frame == self.returnframe:
217 self.user_return(frame, arg)
218 if self.quitting and not self._dbgClient.passive:
219 raise bdb.BdbQuit
220 return self.trace_dispatch
221
222 def dispatch_exception(self, frame, arg):
223 """
224 Reimplemented from bdb.py to always call user_exception.
225
226 @param frame The current stack frame.
227 @param arg The arguments
228 @return local trace function
229 """
230 if not self.__skip_it(frame):
231 self.user_exception(frame, arg)
232 if self.quitting: raise bdb.BdbQuit
233 return self.trace_dispatch
234
235 def set_trace(self, frame = None):
236 """
237 Overridden method of bdb.py to do some special setup.
238
239 @param frame frame to start debugging from
240 """
241 bdb.Bdb.set_trace(self, frame)
242 sys.setprofile(self.profile)
243
244 def set_continue(self, special):
245 """
246 Reimplemented from bdb.py to always get informed of exceptions.
247
248 @param special flag indicating a special continue operation
249 """
250 # Modified version of the one found in bdb.py
251 # Here we only set a new stop frame if it is a normal continue.
252 if not special:
253 self.stopframe = self.botframe
254 self.returnframe = None
255 self.quitting = 0
256
257 def set_quit(self):
258 """
259 Public method to quit.
260
261 It wraps call to bdb to clear the current frame properly.
262 """
263 self.currentFrame = None
264 sys.setprofile(None)
265 bdb.Bdb.set_quit(self)
266
267 def fix_frame_filename(self, frame):
268 """
269 Public method used to fixup the filename for a given frame.
270
271 The logic employed here is that if a module was loaded
272 from a .pyc file, then the correct .py to operate with
273 should be in the same path as the .pyc. The reason this
274 logic is needed is that when a .pyc file is generated, the
275 filename embedded and thus what is readable in the code object
276 of the frame object is the fully qualified filepath when the
277 pyc is generated. If files are moved from machine to machine
278 this can break debugging as the .pyc will refer to the .py
279 on the original machine. Another case might be sharing
280 code over a network... This logic deals with that.
281
282 @param frame the frame object
283 """
284 # get module name from __file__
285 if frame.f_globals.has_key('__file__') and \
286 frame.f_globals['__file__'] and \
287 frame.f_globals['__file__'] == frame.f_code.co_filename:
288 root, ext = os.path.splitext(frame.f_globals['__file__'])
289 if ext == '.pyc' or ext == '.py' or ext == '.pyo':
290 fixedName = root + '.py'
291 if os.path.exists(fixedName):
292 return fixedName
293
294 return frame.f_code.co_filename
295
296 def set_watch(self, cond, temporary=0):
297 """
298 Public method to set a watch expression.
299
300 @param cond expression of the watch expression (string)
301 @param temporary flag indicating a temporary watch expression (boolean)
302 """
303 bp = bdb.Breakpoint("Watch", 0, temporary, cond)
304 if cond.endswith('??created??') or cond.endswith('??changed??'):
305 bp.condition, bp.special = cond.split()
306 else:
307 bp.condition = cond
308 bp.special = ""
309 bp.values = {}
310 if not self.breaks.has_key("Watch"):
311 self.breaks["Watch"] = 1
312 else:
313 self.breaks["Watch"] += 1
314
315 def clear_watch(self, cond):
316 """
317 Public method to clear a watch expression.
318
319 @param cond expression of the watch expression to be cleared (string)
320 """
321 try:
322 possibles = bdb.Breakpoint.bplist["Watch", 0]
323 for i in range(0, len(possibles)):
324 b = possibles[i]
325 if b.cond == cond:
326 b.deleteMe()
327 self.breaks["Watch"] -= 1
328 if self.breaks["Watch"] == 0:
329 del self.breaks["Watch"]
330 break
331 except KeyError:
332 pass
333
334 def get_watch(self, cond):
335 """
336 Public method to get a watch expression.
337
338 @param cond expression of the watch expression to be cleared (string)
339 """
340 possibles = bdb.Breakpoint.bplist["Watch", 0]
341 for i in range(0, len(possibles)):
342 b = possibles[i]
343 if b.cond == cond:
344 return b
345
346 def __do_clearWatch(self, cond):
347 """
348 Private method called to clear a temporary watch expression.
349
350 @param cond expression of the watch expression to be cleared (string)
351 """
352 self.clear_watch(cond)
353 self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond))
354
355 def __effective(self, frame):
356 """
357 Private method to determine, if a watch expression is effective.
358
359 @param frame the current execution frame
360 @return tuple of watch expression and a flag to indicate, that a temporary
361 watch expression may be deleted (bdb.Breakpoint, boolean)
362 """
363 possibles = bdb.Breakpoint.bplist["Watch", 0]
364 for i in range(0, len(possibles)):
365 b = possibles[i]
366 if b.enabled == 0:
367 continue
368 if not b.cond:
369 # watch expression without expression shouldn't occur, just ignore it
370 continue
371 try:
372 val = eval(b.condition, frame.f_globals, frame.f_locals)
373 if b.special:
374 if b.special == '??created??':
375 if b.values[frame][0] == 0:
376 b.values[frame][0] = 1
377 b.values[frame][1] = val
378 return (b, 1)
379 else:
380 continue
381 b.values[frame][0] = 1
382 if b.special == '??changed??':
383 if b.values[frame][1] != val:
384 b.values[frame][1] = val
385 if b.values[frame][2] > 0:
386 b.values[frame][2] -= 1
387 continue
388 else:
389 return (b, 1)
390 else:
391 continue
392 continue
393 if val:
394 if b.ignore > 0:
395 b.ignore -= 1
396 continue
397 else:
398 return (b, 1)
399 except:
400 if b.special:
401 try:
402 b.values[frame][0] = 0
403 except KeyError:
404 b.values[frame] = [0, None, b.ignore]
405 continue
406 return (None, None)
407
408 def break_here(self, frame):
409 """
410 Reimplemented from bdb.py to fix the filename from the frame.
411
412 See fix_frame_filename for more info.
413
414 @param frame the frame object
415 @return flag indicating the break status (boolean)
416 """
417 filename = self.canonic(self.fix_frame_filename(frame))
418 if not self.breaks.has_key(filename) and not self.breaks.has_key("Watch"):
419 return 0
420
421 if self.breaks.has_key(filename):
422 lineno = frame.f_lineno
423 if lineno in self.breaks[filename]:
424 # flag says ok to delete temp. bp
425 (bp, flag) = bdb.effective(filename, lineno, frame)
426 if bp:
427 self.currentbp = bp.number
428 if (flag and bp.temporary):
429 self.__do_clear(filename, lineno)
430 return 1
431
432 if self.breaks.has_key("Watch"):
433 # flag says ok to delete temp. bp
434 (bp, flag) = self.__effective(frame)
435 if bp:
436 self.currentbp = bp.number
437 if (flag and bp.temporary):
438 self.__do_clearWatch(bp.cond)
439 return 1
440
441 return 0
442
443 def break_anywhere(self, frame):
444 """
445 Reimplemented from bdb.py to do some special things.
446
447 These speciality is to fix the filename from the frame
448 (see fix_frame_filename for more info).
449
450 @param frame the frame object
451 @return flag indicating the break status (boolean)
452 """
453 return self.breaks.has_key(
454 self.canonic(self.fix_frame_filename(frame))) or \
455 (self.breaks.has_key("Watch") and self.breaks["Watch"])
456
457 def get_break(self, filename, lineno):
458 """
459 Reimplemented from bdb.py to get the first breakpoint of a particular line.
460
461 Because eric4 supports only one breakpoint per line, this overwritten
462 method will return this one and only breakpoint.
463
464 @param filename the filename of the bp to retrieve (string)
465 @param ineno the linenumber of the bp to retrieve (integer)
466 @return breakpoint or None, if there is no bp
467 """
468 filename = self.canonic(filename)
469 return self.breaks.has_key(filename) and \
470 lineno in self.breaks[filename] and \
471 bdb.Breakpoint.bplist[filename, lineno][0] or None
472
473 def __do_clear(self, filename, lineno):
474 """
475 Private method called to clear a temporary breakpoint.
476
477 @param filename name of the file the bp belongs to
478 @param lineno linenumber of the bp
479 """
480 self.clear_break(filename, lineno)
481 self._dbgClient.write('%s%s,%d\n' % (ResponseClearBreak, filename, lineno))
482
483 def getStack(self):
484 """
485 Public method to get the stack.
486
487 @return list of lists with file name (string), line number (integer)
488 and function name (string)
489 """
490 fr = self.cFrame
491 stack = []
492 while fr is not None:
493 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
494 fline = fr.f_lineno
495 ffunc = fr.f_code.co_name
496
497 if ffunc == '?':
498 ffunc = ''
499
500 stack.append([fname, fline, ffunc])
501
502 if fr == self._dbgClient.mainFrame:
503 fr = None
504 else:
505 fr = fr.f_back
506
507 return stack
508
509 def user_line(self, frame):
510 """
511 Reimplemented to handle the program about to execute a particular line.
512
513 @param frame the frame object
514 """
515 line = frame.f_lineno
516
517 # We never stop on line 0.
518 if line == 0:
519 return
520
521 fn = self._dbgClient.absPath(self.fix_frame_filename(frame))
522
523 # See if we are skipping at the start of a newly loaded program.
524 if self._dbgClient.mainFrame is None:
525 if fn != self._dbgClient.getRunning():
526 return
527 self._dbgClient.mainFrame = frame
528
529 self.currentFrame = frame
530
531 fr = frame
532 stack = []
533 while fr is not None:
534 # Reset the trace function so we can be sure
535 # to trace all functions up the stack... This gets around
536 # problems where an exception/breakpoint has occurred
537 # but we had disabled tracing along the way via a None
538 # return from dispatch_call
539 fr.f_trace = self.trace_dispatch
540 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
541 fline = fr.f_lineno
542 ffunc = fr.f_code.co_name
543
544 if ffunc == '?':
545 ffunc = ''
546
547 stack.append([fname, fline, ffunc])
548
549 if fr == self._dbgClient.mainFrame:
550 fr = None
551 else:
552 fr = fr.f_back
553
554 self.__isBroken = True
555
556 self._dbgClient.write('%s%s\n' % (ResponseLine, unicode(stack)))
557 self._dbgClient.eventLoop()
558
559 def user_exception(self,frame,(exctype,excval,exctb),unhandled=0):
560 """
561 Reimplemented to report an exception to the debug server.
562
563 @param frame the frame object
564 @param exctype the type of the exception
565 @param excval data about the exception
566 @param exctb traceback for the exception
567 @param unhandled flag indicating an uncaught exception
568 """
569 if exctype in [SystemExit, bdb.BdbQuit]:
570 atexit._run_exitfuncs()
571 self._dbgClient.progTerminated(excval)
572 return
573
574 elif exctype in [SyntaxError, IndentationError]:
575 try:
576 message, (filename, linenr, charnr, text) = excval
577 except ValueError:
578 exclist = []
579 else:
580 exclist = [message, [filename, linenr, charnr]]
581
582 self._dbgClient.write("%s%s\n" % (ResponseSyntax, unicode(exclist)))
583
584 else:
585 if type(exctype) in [types.ClassType, # Python up to 2.4
586 types.TypeType]: # Python 2.5+
587 exctype = exctype.__name__
588
589 if excval is None:
590 excval = ''
591
592 if unhandled:
593 exctypetxt = "unhandled %s" % unicode(exctype)
594 else:
595 exctypetxt = unicode(exctype)
596 try:
597 exclist = [exctypetxt,
598 unicode(excval).encode(self._dbgClient.getCoding())]
599 except TypeError:
600 exclist = [exctypetxt, str(excval)]
601
602 if exctb:
603 frlist = self.__extract_stack(exctb)
604 frlist.reverse()
605
606 self.currentFrame = frlist[0]
607
608 for fr in frlist:
609 filename = self._dbgClient.absPath(self.fix_frame_filename(fr))
610 linenr = fr.f_lineno
611
612 if os.path.basename(filename).startswith("DebugClient") or \
613 os.path.basename(filename) == "bdb.py":
614 break
615
616 exclist.append([filename, linenr])
617
618 self._dbgClient.write("%s%s\n" % (ResponseException, unicode(exclist)))
619
620 if exctb is None:
621 return
622
623 self._dbgClient.eventLoop()
624
625 def __extract_stack(self, exctb):
626 """
627 Private member to return a list of stack frames.
628
629 @param exctb exception traceback
630 @return list of stack frames
631 """
632 tb = exctb
633 stack = []
634 while tb is not None:
635 stack.append(tb.tb_frame)
636 tb = tb.tb_next
637 tb = None
638 return stack
639
640 def user_return(self,frame,retval):
641 """
642 Reimplemented to report program termination to the debug server.
643
644 @param frame the frame object
645 @param retval the return value of the program
646 """
647 # The program has finished if we have just left the first frame.
648 if frame == self._dbgClient.mainFrame and \
649 self._mainThread:
650 atexit._run_exitfuncs()
651 self._dbgClient.progTerminated(retval)
652 elif frame is not self.stepFrame:
653 self.stepFrame = None
654 self.user_line(frame)
655
656 def stop_here(self,frame):
657 """
658 Reimplemented to filter out debugger files.
659
660 Tracing is turned off for files that are part of the
661 debugger that are called from the application being debugged.
662
663 @param frame the frame object
664 @return flag indicating whether the debugger should stop here
665 """
666 if self.__skip_it(frame):
667 return 0
668 return bdb.Bdb.stop_here(self,frame)
669
670 def __skip_it(self, frame):
671 """
672 Private method to filter out debugger files.
673
674 Tracing is turned off for files that are part of the
675 debugger that are called from the application being debugged.
676
677 @param frame the frame object
678 @return flag indicating whether the debugger should skip this frame
679 """
680 fn = self.fix_frame_filename(frame)
681
682 # Eliminate things like <string> and <stdin>.
683 if fn[0] == '<':
684 return 1
685
686 #XXX - think of a better way to do this. It's only a convience for
687 #debugging the debugger - when the debugger code is in the current
688 #directory.
689 if os.path.basename(fn) in [\
690 'AsyncFile.py', 'AsyncIO.py',
691 'DebugConfig.py', 'DCTestResult.py',
692 'DebugBase.py', 'DebugClientBase.py',
693 'DebugClientCapabilities.py', 'DebugClient.py',
694 'DebugClientThreads.py', 'DebugProtocol.py',
695 'DebugThread.py', 'FlexCompleter.py',
696 'PyProfile.py'] or \
697 os.path.dirname(fn).endswith("coverage"):
698 return 1
699
700 if self._dbgClient.shouldSkip(fn):
701 return 1
702
703 return 0
704
705 def isBroken(self):
706 """
707 Public method to return the broken state of the debugger.
708
709 @return flag indicating the broken state (boolean)
710 """
711 return self.__isBroken
712
713 def getEvent(self):
714 """
715 Public method to return the last debugger event.
716
717 @return last debugger event (string)
718 """
719 return self.__event

eric ide

mercurial