AMW 2011-11-27
I used the technique described in Steppin' out and A basic debugger for yet another approach to debugging.
Tkcon already contains an interactive debugger that can be invoked by instrumenting your script with calls to idebug break [L1 ].
While this already is a very helpful tool, it requires modification of your existing code whenever you want to set a new breakpoint.
After sourcing the script shown below into Tkcon, you can enter the command bpadd procname at the Tkcon prompt to add a breakpoint at every call to procname without changing your code.
As soon as you run your code and procname is called, the interactive debugger is started.
Type ? to get idebug's menu, c to proceed until the next call to procname, or use any other existing command or feature of idebug.
Moreover, you can call s to 'step into' the intercepted proc, or r to return from it and see the results it produced.
#===============================================================================
# This is an extension to the 'idebug' command in tkcon.tcl.
# Please source this script into your tkcon session.
#===============================================================================
#-------------------------------------------------------------------------------
# bpadd
# add a breakpoint for calls to 'procname'
#-------------------------------------------------------------------------------
proc bpadd { procname } {
global BP
set BP($procname,step) 0
trace add execution $procname enter "BREAKPOINT $procname"
trace add execution $procname enterstep "BREAKPOINT $procname"
trace add execution $procname leave "BREAKPOINT $procname"
idebug on
}
#-------------------------------------------------------------------------------
# bplist
# return a list of active breakpoints
#-------------------------------------------------------------------------------
proc bplist { } {
global BP
set procs ""
foreach item [array names BP "*,step"] {
regsub {,step$} $item "" procname
lappend procs $procname
}
return [lsort $procs]
}
#-------------------------------------------------------------------------------
# bpdel
# delete breakpoint from calls to 'procname'
# if procname is not given, it defaults to the 'current' breakpoint
#-------------------------------------------------------------------------------
proc bpdel { { procname "" } } {
global BP
if { $procname == "" } { set procname $BP(procname) }
trace remove execution $procname enter "BREAKPOINT $procname"
trace remove execution $procname enterstep "BREAKPOINT $procname"
trace remove execution $procname leave "BREAKPOINT $procname"
catch { unset BP($procname,step) }
}
#-------------------------------------------------------------------------------
# dbgStep
# step into procname, i.e. call idebug at trace 'enterstep'
#-------------------------------------------------------------------------------
proc dbgStep { { procname "" } } {
global BP IDEBUG
if { $procname == "" } { set procname $BP(procname) }
set BP($procname,step) 2
# The following statement causes idebug to continue, but this requires
# a small modification in tkcon.tcl (line 4287 in Tkcon 2.5):
# in proc 'idebug' in the switch handling $opt="break", replace
# 'while 1' for the input loop with 'while { $IDEBUG(debugging) }'
set IDEBUG(debugging) 0
return ""
}
#-------------------------------------------------------------------------------
# dbgReturn
# return from procname
#-------------------------------------------------------------------------------
proc dbgReturn { { procname "" } } {
global BP IDEBUG
if { $procname == "" } { set procname $BP(procname) }
set BP($procname,step) 1
set IDEBUG(debugging) 0
return ""
}
#-------------------------------------------------------------------------------
# shortcuts for use within 'idebug':
# s - step into proc
# r - return from proc
#-------------------------------------------------------------------------------
proc s { args } { dbgStep {*}$args }
proc r { args } { dbgReturn {*}$args }
#-------------------------------------------------------------------------------
# dumpstr - dump str in the most readable way,
# replacing non-printable characters with their hex-code written as "\xXX"
#-------------------------------------------------------------------------------
proc dumpstr { str } {
set result ""
while { $str != "" } {
if { [regexp -indices {^[-+*/%<=>.,:;|~^°`´!$&@(){}\[\]#'"A-Z_a-z0-9]+} $str igood] } {
# " readable characters, excluding backslash
append result [string range $str 0 [lindex $igood 1]]
set str [string range $str [lindex $igood 1]+1 end]
}
if { $str != "" } {
set char [string index $str 0]
set str [string range $str 1 end]
if { $char == "\\" } {
append result {\\}
} else {
binary scan $char c hex
append result [format "\\x%02X" [expr $hex & 0xff]]
}
}
}
append result $str
return $result
}
#-------------------------------------------------------------------------------
# BREAKPOINT handler
#-------------------------------------------------------------------------------
proc BREAKPOINT { procname cmd args } {
global BP
set BP(procname) $procname
set op [lindex $args end]
set dbg 1
if { $op == "enter" } {
puts stderr "# $cmd\nBREAKPOINT @ $procname $op"
} elseif { $op == "enterstep" && $BP($procname,step) >= 2 } {
puts stderr "# $cmd\nBREAKPOINT @ $procname step"
} elseif { $op == "leave" && $BP($procname,step) >= 1 } {
set code [lindex $args 0]
set result [lindex $args 1]
puts stderr "# $cmd\nBREAKPOINT @ $procname leave (code $code, result \"[dumpstr $result]\")"
} else {
set dbg 0
}
if { $dbg } {
set BP($procname,step) 0
uplevel idebug break
}
}
#===============================================================================
# end of file
#===============================================================================Of course, this could be added to tkcon.tcl in order to fully intergrate with idebug.