Here is a late night attempt at writing a small wish based debugger. It isn't elegant but it helped me out a bit. The idea of inserting breakpoints as "bp" was stolen/influenced by A minimal debugger.
At the bottom you will find some test code that exercises most of the features. Read it before you run it (for context).
# wdbg - A simple debugger for wish based applications. # # Author: Todd Coram # # Commands: # bp id - set a break point and output 'id' to identify it. You are # then allowed to interactively enter Tcl commands in the debugger # window. # # try id block - try and execute 'block'. If it raises and error, enter # the debugger window under the break point guise of 'id'. # # log_trace var ?ops? ?state? - If 'state' is "on" (default), then trace # the variable named 'var' based on 'ops', where 'ops' can be: # r - read, w - write or u - unset (default "w"). If 'state' is # "off", then turn that particular trace off. Tracing output is sent # to the debugger window. # # log - send arbitrary output to the debugger window. # # Plus, the following debugger commands (in addition to plain-old tcl): # i - show the contents of the current local variables. # l - show lines surrounding the breakpoint (only in procs) # c - continue execution namespace eval ::wdbg { variable got_cmd 0 namespace export bp log_trace log show_locals try } proc ::wdbg::bp {id} { variable got_cmd ::wdbg::_raise_window ::wdbg::log "\n*Breakpoint: $id" cmdTag .wdbg.e configure -state normal while {1} { set res [catch {info level -1} level] if {$res != 0} { set level 0 } ::wdbg::log "$level> " cmdTag -nonewline .wdbg.f.t yview moveto 1.0 .wdbg.e selection range 0 end focus .wdbg.e tkwait variable ::wdbg::got_cmd set line [.wdbg.e get] set cmd [lindex $line 0] set args [lindex $line 1] switch -- $cmd { "c" { .wdbg.e configure -state disabled; return } "i" { set cmd ::wdbg::show_locals} "l" { set cmd "::wdbg::_show_lines {$id} $args"} "help" { ::wdbg::log "Commands:" ::wdbg::log "\tc - continue" ::wdbg::log "\ti - info on local variables" ::wdbg::log "\tl ?n? - list proc source around the breakpoint" ::wdbg::log "\tuse \"set var\" to retrieve a variable's content." ::wdbg::log "\talmost any Tcl command is valid here..." continue } default { set cmd [.wdbg.e get] } } ::wdbg::log "$cmd" cmdTag catch {uplevel 1 $cmd} result ::wdbg::log "$result" resultTag } } proc ::wdbg::try {id block} { set cres [catch {uplevel 1 $block} result] if {$cres != 0} { ::wdbg::log "Failed try: $result" errorTag uplevel 1 [list ::wdbg::bp $id] } } proc ::wdbg::log_trace {var {ops w} {state on}} { set cmd "::wdbg::_log_trace $var" if {$state == "off"} { uplevel 1 ::trace vdelete $var $ops [list $cmd] return } uplevel 1 ::trace variable $var $ops [list $cmd] } proc ::wdbg::log {str {tag logTag} {opt ""}} { ::wdbg::_raise_window .wdbg.f.t insert end "$str" $tag if {$opt == "-nonewline"} { } else { .wdbg.f.t insert end "\n" } .wdbg.f.t yview moveto 1.0 } proc ::wdbg::show_locals {} { set vars [uplevel 1 {info local}] if {[llength $vars] == 0} { ::wdbg::log "none" resultTag return } ::wdbg::log "proc [uplevel 1 {info level 0}]'s local variables:" resultTag foreach var $vars { upvar $var value set _value {} if {![uplevel 1 info exists $var]} { set _value "\#unset\#" } else { set _value $value } ::wdbg::log "\t$var = $_value" resultTag } } proc ::wdbg::_show_lines {id {lines 6}} { if {[catch [uplevel 1 {info body [lindex [info level 0] 0]}] body] != 0} { ::wdbg::log "Sorry, I can only show source for procs." return } set blist [split $body "\n"] set lidx [lsearch -glob $blist "*bp*$id*"] if {$lidx == -1} { set lidx [lsearch -glob $blist "*try*$id*"] if {$lidx == -1} { ::wdbg::log "Can't find breakpoint in source" errorTag return } } set range [expr $lines / 2] set blist [lreplace $blist $lidx $lidx "--> [lindex $blist $lidx]"] set lstr [lrange $blist [expr $lidx - $range] [expr $lidx + $range]] set str [join $lstr "\n"] ::wdbg::log "$str" \ resultTag } proc ::wdbg::_raise_window {} { if {![winfo exists .wdbg]} { # Create the breakpoint window # toplevel .wdbg entry .wdbg.e -width 80 bind .wdbg.e <Key-Return> [list set ::wdbg::got_cmd 1] .wdbg.e configure -state disabled frame .wdbg.f text .wdbg.f.t -width 80 -height 20 -yscrollcommand {.wdbg.f.sb set} \ -wrap word -takefocus 0 bind .wdbg.f.t <KeyPress> {break} # Color scheme for the text window # .wdbg.f.t tag configure traceTag -foreground orange .wdbg.f.t tag configure errorTag -foreground red .wdbg.f.t tag configure logTag -foreground darkgreen .wdbg.f.t tag configure resultTag -foreground blue .wdbg.f.t tag configure cmdTag -foreground [.wdbg.f.t cget -foreground] scrollbar .wdbg.f.sb -orient vertical -command {.wdbg.f.t yview} pack .wdbg.f.t -anchor nw -expand yes -fill both -side left pack .wdbg.f.sb -anchor e -expand no -fill y -side left pack .wdbg.e -side top -fill x -expand no -anchor nw pack .wdbg.f -fill both -expand yes -anchor nw wm title .wdbg "Wdbg - A Simple Wish-based Debugger" # "continue" if someone tries to close the debugger window... # wm protocol .wdbg WM_DELETE_WINDOW { .wdbg.e delete 0 end; \ .wdbg.e insert end "c"; \ set ::wdbg::got_cmd 1; \ wm withdraw .wdbg } # first command defaults to "help"... # .wdbg.e insert end "help" } wm deiconify .wdbg raise .wdbg focus .wdbg.e } proc ::wdbg::_log_trace {vname name1 name2 op} { upvar $vname vn ::wdbg::log "trace -> $name1 = ($vn)" traceTag } ############# # Test if { [info exists argv0] && [string compare [info script] $argv0] == 0} { namespace import ::wdbg::* log "starting" bp "top level" proc foo {} { set x what set y huh? set n1 22 set n2 43 try "this works" {show_locals} try "this fails" {show_local} log_trace n1 w incr n1 incr n1 incr n1 incr n1 bp "try changing the local variables" show_locals } proc bar {} { bp "in bar" foo } bar }
If it looks useful, have fun -- Todd Coram