DebugClients/Python3/DebugBase.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 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('{0!s}\n'.format(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 = True
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 True, 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('bdb.Bdb.dispatch: unknown debugging event: ', repr(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._set_stopinfo(self.botframe, None)
254 else:
255 self._set_stopinfo(self.stopframe, None)
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 '__file__' in frame.f_globals 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 in ['.pyc', '.py', '.py3', '.pyo']:
290 fixedName = root + '.py'
291 if os.path.exists(fixedName):
292 return fixedName
293
294 fixedName = root + '.py3'
295 if os.path.exists(fixedName):
296 return fixedName
297
298 return frame.f_code.co_filename
299
300 def set_watch(self, cond, temporary = False):
301 """
302 Public method to set a watch expression.
303
304 @param cond expression of the watch expression (string)
305 @param temporary flag indicating a temporary watch expression (boolean)
306 """
307 bp = bdb.Breakpoint("Watch", 0, temporary, cond)
308 if cond.endswith('??created??') or cond.endswith('??changed??'):
309 bp.condition, bp.special = cond.split()
310 else:
311 bp.condition = cond
312 bp.special = ""
313 bp.values = {}
314 if "Watch" not in self.breaks:
315 self.breaks["Watch"] = 1
316 else:
317 self.breaks["Watch"] += 1
318
319 def clear_watch(self, cond):
320 """
321 Public method to clear a watch expression.
322
323 @param cond expression of the watch expression to be cleared (string)
324 """
325 try:
326 possibles = bdb.Breakpoint.bplist["Watch", 0]
327 for i in range(0, len(possibles)):
328 b = possibles[i]
329 if b.cond == cond:
330 b.deleteMe()
331 self.breaks["Watch"] -= 1
332 if self.breaks["Watch"] == 0:
333 del self.breaks["Watch"]
334 break
335 except KeyError:
336 pass
337
338 def get_watch(self, cond):
339 """
340 Public method to get a watch expression.
341
342 @param cond expression of the watch expression to be cleared (string)
343 """
344 possibles = bdb.Breakpoint.bplist["Watch", 0]
345 for i in range(0, len(possibles)):
346 b = possibles[i]
347 if b.cond == cond:
348 return b
349
350 def __do_clearWatch(self, cond):
351 """
352 Private method called to clear a temporary watch expression.
353
354 @param cond expression of the watch expression to be cleared (string)
355 """
356 self.clear_watch(cond)
357 self._dbgClient.write('{0}{1}\n'.format(ResponseClearWatch, cond))
358
359 def __effective(self, frame):
360 """
361 Private method to determine, if a watch expression is effective.
362
363 @param frame the current execution frame
364 @return tuple of watch expression and a flag to indicate, that a temporary
365 watch expression may be deleted (bdb.Breakpoint, boolean)
366 """
367 possibles = bdb.Breakpoint.bplist["Watch", 0]
368 for i in range(0, len(possibles)):
369 b = possibles[i]
370 if not b.enabled:
371 continue
372 if not b.cond:
373 # watch expression without expression shouldn't occur, just ignore it
374 continue
375 try:
376 val = eval(b.condition, frame.f_globals, frame.f_locals)
377 if b.special:
378 if b.special == '??created??':
379 if b.values[frame][0] == 0:
380 b.values[frame][0] = 1
381 b.values[frame][1] = val
382 return (b, True)
383 else:
384 continue
385 b.values[frame][0] = 1
386 if b.special == '??changed??':
387 if b.values[frame][1] != val:
388 b.values[frame][1] = val
389 if b.values[frame][2] > 0:
390 b.values[frame][2] -= 1
391 continue
392 else:
393 return (b, True)
394 else:
395 continue
396 continue
397 if val:
398 if b.ignore > 0:
399 b.ignore -= 1
400 continue
401 else:
402 return (b, True)
403 except:
404 if b.special:
405 try:
406 b.values[frame][0] = 0
407 except KeyError:
408 b.values[frame] = [0, None, b.ignore]
409 continue
410
411 return (None, False)
412
413 def break_here(self, frame):
414 """
415 Reimplemented from bdb.py to fix the filename from the frame.
416
417 See fix_frame_filename for more info.
418
419 @param frame the frame object
420 @return flag indicating the break status (boolean)
421 """
422 filename = self.canonic(self.fix_frame_filename(frame))
423 if filename not in self.breaks and "Watch" not in self.breaks:
424 return False
425
426 if filename in self.breaks:
427 lineno = frame.f_lineno
428 if lineno not in self.breaks[filename]:
429 # The line itself has no breakpoint, but maybe the line is the
430 # first line of a function with breakpoint set by function name.
431 lineno = frame.f_code.co_firstlineno
432 if lineno in self.breaks[filename]:
433 # flag says ok to delete temp. bp
434 (bp, flag) = bdb.effective(filename, lineno, frame)
435 if bp:
436 self.currentbp = bp.number
437 if (flag and bp.temporary):
438 self.__do_clear(filename, lineno)
439 return True
440
441 if "Watch" in self.breaks:
442 # flag says ok to delete temp. bp
443 (bp, flag) = self.__effective(frame)
444 if bp:
445 self.currentbp = bp.number
446 if (flag and bp.temporary):
447 self.__do_clearWatch(bp.cond)
448 return True
449
450 return False
451
452 def break_anywhere(self, frame):
453 """
454 Reimplemented from bdb.py to do some special things.
455
456 These speciality is to fix the filename from the frame
457 (see fix_frame_filename for more info).
458
459 @param frame the frame object
460 @return flag indicating the break status (boolean)
461 """
462 return \
463 self.canonic(self.fix_frame_filename(frame)) in self.breaks or \
464 ("Watch" in self.breaks and self.breaks["Watch"])
465
466 def get_break(self, filename, lineno):
467 """
468 Reimplemented from bdb.py to get the first breakpoint of a particular line.
469
470 Because eric4 supports only one breakpoint per line, this overwritten
471 method will return this one and only breakpoint.
472
473 @param filename the filename of the bp to retrieve (string)
474 @param lineno the linenumber of the bp to retrieve (integer)
475 @return breakpoint or None, if there is no bp
476 """
477 filename = self.canonic(filename)
478 return filename in self.breaks and \
479 lineno in self.breaks[filename] and \
480 bdb.Breakpoint.bplist[filename, lineno][0] or None
481
482 def __do_clear(self, filename, lineno):
483 """
484 Private method called to clear a temporary breakpoint.
485
486 @param filename name of the file the bp belongs to
487 @param lineno linenumber of the bp
488 """
489 self.clear_break(filename, lineno)
490 self._dbgClient.write('{0}{1},{2:d}\n'.format(
491 ResponseClearBreak, filename, lineno))
492
493 def getStack(self):
494 """
495 Public method to get the stack.
496
497 @return list of lists with file name (string), line number (integer)
498 and function name (string)
499 """
500 fr = self.cFrame
501 stack = []
502 while fr is not None:
503 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
504 fline = fr.f_lineno
505 ffunc = fr.f_code.co_name
506
507 if ffunc == '?':
508 ffunc = ''
509
510 stack.append([fname, fline, ffunc])
511
512 if fr == self._dbgClient.mainFrame:
513 fr = None
514 else:
515 fr = fr.f_back
516
517 return stack
518
519 def user_line(self, frame):
520 """
521 Reimplemented to handle the program about to execute a particular line.
522
523 @param frame the frame object
524 """
525 line = frame.f_lineno
526
527 # We never stop on line 0.
528 if line == 0:
529 return
530
531 fn = self._dbgClient.absPath(self.fix_frame_filename(frame))
532
533 # See if we are skipping at the start of a newly loaded program.
534 if self._dbgClient.mainFrame is None:
535 if fn != self._dbgClient.getRunning():
536 return
537 self._dbgClient.mainFrame = frame
538
539 self.currentFrame = frame
540
541 fr = frame
542 stack = []
543 while fr is not None:
544 # Reset the trace function so we can be sure
545 # to trace all functions up the stack... This gets around
546 # problems where an exception/breakpoint has occurred
547 # but we had disabled tracing along the way via a None
548 # return from dispatch_call
549 fr.f_trace = self.trace_dispatch
550 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
551 fline = fr.f_lineno
552 ffunc = fr.f_code.co_name
553
554 if ffunc == '?':
555 ffunc = ''
556
557 stack.append([fname, fline, ffunc])
558
559 if fr == self._dbgClient.mainFrame:
560 fr = None
561 else:
562 fr = fr.f_back
563
564 self.__isBroken = True
565
566 self._dbgClient.write('{0}{1}\n'.format(ResponseLine, str(stack)))
567 self._dbgClient.eventLoop()
568
569 def user_exception(self, frame, excinfo, unhandled = False):
570 """
571 Reimplemented to report an exception to the debug server.
572
573 @param frame the frame object
574 @param excinfo information about the exception
575 @param unhandled flag indicating an uncaught exception
576 """
577 exctype, excval, exctb = excinfo
578 if exctype in [SystemExit, bdb.BdbQuit]:
579 atexit._run_exitfuncs()
580 self._dbgClient.progTerminated(excval)
581 return
582
583 elif exctype in [SyntaxError, IndentationError]:
584 try:
585 message, (filename, linenr, charnr, text) = excval.args
586 except ValueError:
587 exclist = []
588 else:
589 exclist = [message, [filename, linenr, charnr]]
590
591 self._dbgClient.write("{0}{1}\n".format(ResponseSyntax, str(exclist)))
592
593 else:
594 exctype = self.__extractExceptionName(exctype)
595
596 if excval is None:
597 excval = ''
598
599 if unhandled:
600 exctypetxt = "unhandled {0!s}".format(str(exctype))
601 else:
602 exctypetxt = str(exctype)
603 try:
604 exclist = [exctypetxt,
605 str(excval).encode(
606 self._dbgClient.getCoding(), 'backslashreplace')]
607 except TypeError:
608 exclist = [exctypetxt, str(excval)]
609
610 if exctb:
611 frlist = self.__extract_stack(exctb)
612 frlist.reverse()
613
614 self.currentFrame = frlist[0]
615
616 for fr in frlist:
617 filename = self._dbgClient.absPath(self.fix_frame_filename(fr))
618 linenr = fr.f_lineno
619
620 if os.path.basename(filename).startswith("DebugClient") or \
621 os.path.basename(filename) == "bdb.py":
622 break
623
624 exclist.append([filename, linenr])
625
626 self._dbgClient.write("{0}{1}\n".format(ResponseException, str(exclist)))
627
628 if exctb is None:
629 return
630
631 self._dbgClient.eventLoop()
632
633 def __extractExceptionName(self, exctype):
634 """
635 Private method to extract the exception name given the exception
636 type object.
637
638 @param exctype type of the exception
639 """
640 return repr(exctype).replace("<class '", "").replace("'>", "")
641
642 def __extract_stack(self, exctb):
643 """
644 Private member to return a list of stack frames.
645
646 @param exctb exception traceback
647 @return list of stack frames
648 """
649 tb = exctb
650 stack = []
651 while tb is not None:
652 stack.append(tb.tb_frame)
653 tb = tb.tb_next
654 tb = None
655 return stack
656
657 def user_return(self, frame, retval):
658 """
659 Reimplemented to report program termination to the debug server.
660
661 @param frame the frame object
662 @param retval the return value of the program
663 """
664 # The program has finished if we have just left the first frame.
665 if frame == self._dbgClient.mainFrame and \
666 self._mainThread:
667 atexit._run_exitfuncs()
668 self._dbgClient.progTerminated(retval)
669 elif frame is not self.stepFrame:
670 self.stepFrame = None
671 self.user_line(frame)
672
673 def stop_here(self, frame):
674 """
675 Reimplemented to filter out debugger files.
676
677 Tracing is turned off for files that are part of the
678 debugger that are called from the application being debugged.
679
680 @param frame the frame object
681 @return flag indicating whether the debugger should stop here
682 """
683 if self.__skip_it(frame):
684 return False
685
686 return bdb.Bdb.stop_here(self,frame)
687
688 def __skip_it(self, frame):
689 """
690 Private method to filter out debugger files.
691
692 Tracing is turned off for files that are part of the
693 debugger that are called from the application being debugged.
694
695 @param frame the frame object
696 @return flag indicating whether the debugger should skip this frame
697 """
698 fn = self.fix_frame_filename(frame)
699
700 # Eliminate things like <string> and <stdin>.
701 if fn[0] == '<':
702 return 1
703
704 #XXX - think of a better way to do this. It's only a convience for
705 #debugging the debugger - when the debugger code is in the current
706 #directory.
707 if os.path.basename(fn) in [\
708 'AsyncFile.py', 'AsyncIO.py',
709 'DebugConfig.py', 'DCTestResult.py',
710 'DebugBase.py', 'DebugClientBase.py',
711 'DebugClientCapabilities.py', 'DebugClient.py',
712 'DebugClientThreads.py', 'DebugProtocol.py',
713 'DebugThread.py', 'FlexCompleter.py',
714 'PyProfile.py'] or \
715 os.path.dirname(fn).endswith("coverage"):
716 return True
717
718 if self._dbgClient.shouldSkip(fn):
719 return True
720
721 return False
722
723 def isBroken(self):
724 """
725 Public method to return the broken state of the debugger.
726
727 @return flag indicating the broken state (boolean)
728 """
729 return self.__isBroken
730
731 def getEvent(self):
732 """
733 Public method to return the last debugger event.
734
735 @return last debugger event (string)
736 """
737 return self.__event

eric ide

mercurial