2 # -*- coding: utf-8 -*-
4 # codimension - graphics python two-way code editor and analyzer
5 # Copyright (C) 2010 Sergey Satskiy <sergey.satskiy@gmail.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # The file was taken from eric 4 and adopted for codimension.
26 # Copyright (c) 2002 - 2012 Detlev Offenbach <detlev@die-offenbachs.de>
30 Module implementing the debug base class.
40 from protocol_cdm_dbg
import ( ResponseClearWatch
, ResponseClearBreak
,
41 ResponseLine
, ResponseSyntax
, ResponseException
)
46 def setRecursionLimit( limit
):
47 " Sets the recursion limit "
48 global RECURSION_LIMIT
49 RECURSION_LIMIT
= limit
52 class DebugBase( bdb
.Bdb
):
54 Base class of the debugger - the part which is external to IDE
56 Provides simple wrapper methods around bdb for the 'owning' client to
60 def __init__( self
, dbgClient
):
64 @param dbgClient the owning client
66 bdb
.Bdb
.__init
__( self
)
68 self
._dbgClient
= dbgClient
71 self
.breaks
= self
._dbgClient
.breakpoints
77 # current frame we are at
78 self
.currentFrame
= None
79 self
.currentFrameLocals
= None
81 # frame that we are stepping in, can be different than currentFrame
84 # provide a hook to perform a hard breakpoint
86 # if hasattr(sys, 'breakpoint'): sys.breakpoint()
87 sys
.breakpoint
= self
.set_trace
92 self
.__recursionDepth
= -1
93 self
.setRecursionDepth( inspect
.currentframe() )
96 def getCurrentFrame( self
):
98 Provides the current frame.
100 @return the current frame
102 return self
.currentFrame
104 def getCurrentFrameLocals( self
):
106 Provides the locals dictionary of the current frame.
108 @return locals dictionary of the current frame
110 return self
.currentFrameLocals
112 def step( self
, traceMode
):
114 Performs step in this thread.
116 @param traceMode If it is non-zero, then the step is a step into,
117 otherwise it is a step over.
119 self
.stepFrame
= self
.currentFrame
121 self
.currentFrame
= None
124 self
.set_next( self
.currentFrame
)
129 Performs a step out of the current call
131 self
.stepFrame
= self
.currentFrame
132 self
.set_return( self
.currentFrame
)
135 def go( self
, special
):
139 It resumes the thread stopping only at breakpoints or exceptions.
141 @param special flag indicating a special continue operation
143 self
.currentFrame
= None
144 self
.set_continue( special
)
147 def setRecursionDepth( self
, frame
):
149 Determines the current recursion depth
151 @param frame The current stack frame.
153 self
.__recursionDepth
= 0
154 while frame
is not None:
155 self
.__recursionDepth
+= 1
159 def profile( self
, frame
, event
, arg
):
161 Public method used to trace some stuff independant of the debugger
164 @param frame The current stack frame.
165 @param event The trace event (string)
166 @param arg The arguments
168 if event
== 'return':
169 self
.cFrame
= frame
.f_back
170 self
.__recursionDepth
-= 1
171 elif event
== 'call':
173 self
.__recursionDepth
+= 1
174 if self
.__recursionDepth
> RECURSION_LIMIT
:
176 'maximum recursion depth exceeded\n'
177 '(offending frame is too down the stack)' )
180 def trace_dispatch( self
, frame
, event
, arg
):
182 Reimplemented from bdb.py to do some special things.
184 This specialty is to check the connection to the debug server
185 for new events (i.e. new breakpoints) while we are going through
188 @param frame The current stack frame.
189 @param event The trace event (string)
190 @param arg The arguments
191 @return local trace function """
195 # give the client a chance to push through new break points.
196 self
._dbgClient
.eventPoll()
199 self
.__isBroken
= False
202 return self
.dispatch_line( frame
)
204 return self
.dispatch_call( frame
, arg
)
205 if event
== 'return':
206 return self
.dispatch_return( frame
, arg
)
207 if event
== 'exception':
208 return self
.dispatch_exception( frame
, arg
)
209 if event
== 'c_call':
210 return self
.trace_dispatch
211 if event
== 'c_exception':
212 return self
.trace_dispatch
213 if event
== 'c_return':
214 return self
.trace_dispatch
216 print 'DebugBase.trace_dispatch: unknown debugging event: ' + \
218 return self
.trace_dispatch
220 def dispatch_line( self
, frame
):
222 Reimplemented from bdb.py to do some special things.
224 This speciality is to check the connection to the debug server
225 for new events (i.e. new breakpoints) while we are going through
228 @param frame The current stack frame.
229 @return local trace function
231 if self
.stop_here( frame
) or self
.break_here( frame
):
232 self
.user_line( frame
)
235 return self
.trace_dispatch
237 def dispatch_return( self
, frame
, arg
):
239 Reimplemented from bdb.py to handle passive mode cleanly.
241 @param frame The current stack frame.
242 @param arg The arguments
243 @return local trace function
245 if self
.stop_here( frame
) or frame
== self
.returnframe
:
246 self
.user_return( frame
, arg
)
247 if self
.quitting
and not self
._dbgClient
.passive
:
249 return self
.trace_dispatch
251 def dispatch_exception( self
, frame
, arg
):
253 Reimplemented from bdb.py to always call user_exception.
255 @param frame The current stack frame.
256 @param arg The arguments
257 @return local trace function
259 if not self
.__skip
_it
( frame
):
260 self
.user_exception( frame
, arg
)
263 return self
.trace_dispatch
265 def set_trace( self
, frame
= None ):
267 Overridden method of bdb.py to do some special setup.
269 @param frame frame to start debugging from
271 bdb
.Bdb
.set_trace( self
, frame
)
272 sys
.setprofile( self
.profile
)
275 def set_continue( self
, special
):
277 Reimplemented from bdb.py to always get informed of exceptions.
279 @param special flag indicating a special continue operation
281 # Modified version of the one found in bdb.py
282 # Here we only set a new stop frame if it is a normal continue.
284 self
.stopframe
= self
.botframe
285 self
.returnframe
= None
289 def set_quit( self
):
291 Public method to quit.
293 It wraps call to bdb to clear the current frame properly.
295 self
.currentFrame
= None
296 sys
.setprofile( None )
297 bdb
.Bdb
.set_quit( self
)
300 def fix_frame_filename( self
, frame
):
302 Public method used to fixup the filename for a given frame.
304 The logic employed here is that if a module was loaded
305 from a .pyc file, then the correct .py to operate with
306 should be in the same path as the .pyc. The reason this
307 logic is needed is that when a .pyc file is generated, the
308 filename embedded and thus what is readable in the code object
309 of the frame object is the fully qualified filepath when the
310 pyc is generated. If files are moved from machine to machine
311 this can break debugging as the .pyc will refer to the .py
312 on the original machine. Another case might be sharing
313 code over a network... This logic deals with that.
315 @param frame the frame object
317 # get module name from __file__
318 if frame
.f_globals
.has_key( '__file__' ) and \
319 frame
.f_globals
[ '__file__' ] and \
320 frame
.f_globals
[ '__file__' ] == frame
.f_code
.co_filename
:
321 root
, ext
= os
.path
.splitext( frame
.f_globals
[ '__file__' ] )
322 if ext
in [ '.pyc', '.py', '.pyo' ]:
323 fixedName
= root
+ '.py'
324 if os
.path
.exists( fixedName
):
327 return frame
.f_code
.co_filename
329 def set_watch( self
, cond
, temporary
= 0 ):
331 Public method to set a watch expression
333 @param cond expression of the watch expression (string)
334 @param temporary flag indicating a temporary watch expression (boolean)
336 bpoint
= bdb
.Breakpoint( "Watch", 0, temporary
, cond
)
337 if cond
.endswith( '??created??' ) or cond
.endswith( '??changed??' ):
338 bpoint
.condition
, bpoint
.special
= cond
.split()
340 bpoint
.condition
= cond
343 if not self
.breaks
.has_key( "Watch" ):
344 self
.breaks
[ "Watch" ] = 1
346 self
.breaks
[ "Watch" ] += 1
349 def clear_watch( self
, cond
):
351 Public method to clear a watch expression
353 @param cond expression of the watch expression to be cleared (string)
356 possibles
= bdb
.Breakpoint
.bplist
[ "Watch", 0 ]
357 for i
in xrange( 0, len( possibles
) ):
361 self
.breaks
[ "Watch" ] -= 1
362 if self
.breaks
[ "Watch" ] == 0:
363 del self
.breaks
[ "Watch" ]
369 def get_watch( self
, cond
):
371 Public method to get a watch expression
373 @param cond expression of the watch expression to be cleared (string)
375 possibles
= bdb
.Breakpoint
.bplist
[ "Watch", 0 ]
376 for i
in xrange( 0, len( possibles
) ):
382 def __do_clearWatch( self
, cond
):
384 Private method called to clear a temporary watch expression
386 @param cond expression of the watch expression to be cleared (string)
388 self
.clear_watch( cond
)
389 self
._dbgClient
.write( '%s%s\n' % ( ResponseClearWatch
, cond
) )
392 def __effective( self
, frame
):
394 Determines if a watch expression is effective
396 @param frame the current execution frame
397 @return tuple of watch expression and a flag to indicate, that a temporary
398 watch expression may be deleted (bdb.Breakpoint, boolean)
400 possibles
= bdb
.Breakpoint
.bplist
[ "Watch", 0 ]
401 for i
in xrange( 0, len( possibles
) ):
406 # watch expression without expression shouldn't occur,
410 val
= eval( b
.condition
, frame
.f_globals
, frame
.f_locals
)
412 if b
.special
== '??created??':
413 if b
.values
[ frame
][ 0 ] == 0:
414 b
.values
[ frame
][ 0 ] = 1
415 b
.values
[ frame
][ 1 ] = val
419 b
.values
[ frame
][ 0 ] = 1
420 if b
.special
== '??changed??':
421 if b
.values
[ frame
][ 1 ] != val
:
422 b
.values
[ frame
][ 1 ] = val
423 if b
.values
[ frame
][ 2 ] > 0:
424 b
.values
[ frame
][ 2 ] -= 1
440 b
.values
[ frame
][ 0 ] = 0
442 b
.values
[ frame
] = [ 0, None, b
.ignore
]
444 return ( None, None )
446 def break_here( self
, frame
):
447 """ Reimplemented from bdb.py to fix the filename from the frame
449 See fix_frame_filename for more info.
451 @param frame the frame object
452 @return flag indicating the break status (boolean)
454 filename
= self
.canonic( self
.fix_frame_filename( frame
) )
455 if not self
.breaks
.has_key( filename
) and \
456 not self
.breaks
.has_key( "Watch" ):
459 if self
.breaks
.has_key( filename
):
460 lineno
= frame
.f_lineno
461 if lineno
in self
.breaks
[ filename
]:
462 # flag says ok to delete temp. bp
463 ( bp
, flag
) = bdb
.effective( filename
, lineno
, frame
)
465 self
.currentbp
= bp
.number
466 if ( flag
and bp
.temporary
):
467 self
.__do
_clear
( filename
, lineno
)
470 if self
.breaks
.has_key( "Watch" ):
471 # flag says ok to delete temp. bp
472 ( bp
, flag
) = self
.__effective
( frame
)
474 self
.currentbp
= bp
.number
475 if ( flag
and bp
.temporary
):
476 self
.__do
_clearWatch
( bp
.cond
)
481 def break_anywhere( self
, frame
):
483 Reimplemented from bdb.py to do some special things.
485 These speciality is to fix the filename from the frame
486 (see fix_frame_filename for more info).
488 @param frame the frame object
489 @return flag indicating the break status (boolean)
491 return self
.breaks
.has_key(
492 self
.canonic( self
.fix_frame_filename( frame
) ) ) or \
493 ( self
.breaks
.has_key( "Watch" ) and self
.breaks
[ "Watch" ] )
495 def get_break( self
, filename
, lineno
):
497 Reimplemented from bdb.py to get the first
498 breakpoint of a particular line.
500 Only one breakpoint per line is supported so this overwritten
501 method will return this one and only breakpoint.
503 @param filename the filename of the bp to retrieve (string)
504 @param ineno the linenumber of the bp to retrieve (integer)
505 @return breakpoint or None, if there is no bp
507 filename
= self
.canonic( filename
)
508 return self
.breaks
.has_key( filename
) and \
509 lineno
in self
.breaks
[ filename
] and \
510 bdb
.Breakpoint
.bplist
[ filename
, lineno
][ 0 ] or None
512 def __do_clear( self
, filename
, lineno
):
514 Clears a temporary breakpoint
516 @param filename name of the file the bp belongs to
517 @param lineno linenumber of the bp
519 self
.clear_break( filename
, lineno
)
520 self
._dbgClient
.write( '%s%s,%d\n' % ( ResponseClearBreak
,
524 def getStack( self
):
528 @return list of lists with file name (string), line number (integer)
529 and function name (string)
533 while frame
is not None:
534 fname
= self
._dbgClient
.absPath( \
535 self
.fix_frame_filename( frame
) )
536 fline
= frame
.f_lineno
537 ffunc
= frame
.f_code
.co_name
542 stack
.append( [ fname
, fline
, ffunc
] )
544 if frame
== self
._dbgClient
.mainFrame
:
551 def user_line( self
, frame
):
553 Reimplemented to handle the program about to execute
556 @param frame the frame object
558 line
= frame
.f_lineno
560 # We never stop on line 0.
564 fn
= self
._dbgClient
.absPath( self
.fix_frame_filename( frame
) )
566 # See if we are skipping at the start of a newly loaded program.
567 if self
._dbgClient
.mainFrame
is None:
568 if fn
!= self
._dbgClient
.getRunning():
570 self
._dbgClient
.mainFrame
= frame
572 self
.currentFrame
= frame
573 self
.currentFrameLocals
= frame
.f_locals
574 # remember the locals because it is reinitialized when accessed
578 while fr
is not None:
579 # Reset the trace function so we can be sure
580 # to trace all functions up the stack... This gets around
581 # problems where an exception/breakpoint has occurred
582 # but we had disabled tracing along the way via a None
583 # return from dispatch_call
584 fr
.f_trace
= self
.trace_dispatch
585 fname
= self
._dbgClient
.absPath( self
.fix_frame_filename( fr
) )
587 ffunc
= fr
.f_code
.co_name
592 stack
.append( [ fname
, fline
, ffunc
] )
594 if fr
== self
._dbgClient
.mainFrame
:
599 self
.__isBroken
= True
601 self
._dbgClient
.write( '%s%s\n' % ( ResponseLine
, unicode( stack
) ) )
602 self
._dbgClient
.eventLoop()
604 def user_exception( self
, frame
,
605 ( exctype
, excval
, exctb
),
608 Reimplemented to report an exception to the debug server
610 @param frame the frame object
611 @param exctype the type of the exception
612 @param excval data about the exception
613 @param exctb traceback for the exception
614 @param unhandled flag indicating an uncaught exception
616 if exctype
in [ SystemExit, bdb
.BdbQuit
]:
617 atexit
._run
_exitfuncs
()
620 elif isinstance( excval
, ( unicode, str ) ):
621 self
._dbgClient
.write( excval
)
623 if isinstance( excval
, int ):
624 self
._dbgClient
.progTerminated( excval
)
626 self
._dbgClient
.progTerminated( excval
.code
)
629 elif exctype
in [ SyntaxError, IndentationError ]:
631 message
, ( filename
, linenr
, charnr
, text
) = excval
635 exclist
= [ message
, [ filename
, linenr
, charnr
] ]
637 self
._dbgClient
.write( "%s%s\n" % ( ResponseSyntax
,
638 unicode( exclist
) ) )
641 if type( exctype
) in [ types
.ClassType
, # Python up to 2.4
642 types
.TypeType
]: # Python 2.5+
643 exctype
= exctype
.__name
__
649 exctypetxt
= "unhandled %s" % unicode( exctype
)
651 exctypetxt
= unicode( exctype
)
653 exclist
= [ exctypetxt
,
654 unicode( excval
).encode(
655 self
._dbgClient
.getCoding() ) ]
657 exclist
= [ exctypetxt
, str( excval
) ]
660 frlist
= self
.__extract
_stack
( exctb
)
663 self
.currentFrame
= frlist
[ 0 ]
664 self
.currentFrameLocals
= frlist
[0].f_locals
665 # remember the locals because it is reinitialized when accessed
668 filename
= self
._dbgClient
.absPath(
669 self
.fix_frame_filename( fr
) )
672 fBasename
= os
.path
.basename( filename
)
673 if fBasename
.endswith( "_cdm_dbg.py" ) or \
674 fBasename
== "bdb.py":
677 exclist
.append( [ filename
, linenr
] )
679 self
._dbgClient
.write( "%s%s\n" % ( ResponseException
,
680 unicode( exclist
) ) )
685 self
._dbgClient
.eventLoop()
688 def __extract_stack( self
, exctb
):
690 Provides a list of stack frames
692 @param exctb exception traceback
693 @return list of stack frames
697 while tb
is not None:
698 stack
.append( tb
.tb_frame
)
703 def user_return( self
, frame
, retval
):
705 Reimplemented to report program termination to the debug server.
707 @param frame the frame object
708 @param retval the return value of the program
710 # The program has finished if we have just left the first frame.
711 if frame
== self
._dbgClient
.mainFrame
and \
713 atexit
._run
_exitfuncs
()
714 self
._dbgClient
.progTerminated( retval
)
715 elif frame
is not self
.stepFrame
:
716 self
.stepFrame
= None
717 self
.user_line( frame
)
720 def stop_here( self
, frame
):
722 Reimplemented to filter out debugger files.
724 Tracing is turned off for files that are part of the
725 debugger that are called from the application being debugged.
727 @param frame the frame object
728 @return flag indicating whether the debugger should stop here
730 if self
.__skip
_it
( frame
):
732 return bdb
.Bdb
.stop_here( self
, frame
)
734 def __skip_it( self
, frame
):
736 Private method to filter out debugger files.
738 Tracing is turned off for files that are part of the
739 debugger that are called from the application being debugged.
741 @param frame the frame object
742 @return flag indicating whether the debugger should skip this frame
744 fname
= self
.fix_frame_filename( frame
)
746 # Eliminate things like <string> and <stdin>.
747 if fname
[ 0 ] == '<':
750 #XXX - think of a better way to do this. It's only a convience for
751 #debugging the debugger - when the debugger code is in the current
753 if ( 'debugger' + os
.path
.sep
+ 'client' + os
.path
.sep
) in fname
:
756 if self
._dbgClient
.shouldSkip( fname
):
761 def isBroken( self
):
763 Public method to return the broken state of the debugger.
765 @return flag indicating the broken state (boolean)
767 return self
.__isBroken
769 def getEvent( self
):
771 Public method to return the last debugger event.
773 @return last debugger event (string)