~ chicken-core (chicken-5) /feathers.tcl


   1#!/usr/bin/env wish
   2#
   3# a graphical debugger for compiled CHICKEN programs
   4#
   5# Copyright (c) 2015-2022, The CHICKEN Team
   6# All rights reserved.
   7#
   8# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
   9# conditions are met:
  10#
  11#   Redistributions of source code must retain the above copyright notice, this list of conditions and the following
  12#     disclaimer.
  13#   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  14#     disclaimer in the documentation and/or other materials provided with the distribution.
  15#   Neither the name of the author nor the names of its contributors may be used to endorse or promote
  16#     products derived from this software without specific prior written permission.
  17#
  18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
  19# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  20# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
  21# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  23# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  25# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26# POSSIBILITY OF SUCH DAMAGE.
  27
  28
  29set version 0
  30set protocol_version 1
  31set debugger_port 9999
  32
  33set events(1) call
  34set events(2) assign
  35set events(3) gc
  36set events(4) entry
  37set events(5) signal
  38set events(6) connect
  39set events(7) listen
  40set events(8) interrupted
  41
  42set reply(SETMASK) 1
  43set reply(TERMINATE) 2
  44set reply(CONTINUE) 3
  45set reply(SET_BREAKPOINT) 4
  46set reply(CLEAR_BREAKPOINT) 5
  47set reply(LIST_EVENTS) 6
  48set reply(GET_BYTES) 7
  49set reply(GET_AV) 8
  50set reply(GET_SLOTS) 9
  51set reply(GET_GLOBAL) 10
  52set reply(GET_STATS) 11
  53set reply(GET_TRACE) 12
  54
  55set colors(header_foreground) white
  56set colors(header_background) black
  57set colors(text_foreground) black
  58set colors(text_background) gray90
  59set colors(event_foreground) black
  60set colors(event_background) white
  61set colors(breakpoint_foreground) white
  62set colors(breakpoint_background) DarkRed
  63set colors(highlight_foreground) white
  64set colors(highlight_background) CornflowerBlue
  65set colors(mark_foreground) black
  66set colors(mark_background) yellow
  67set colors(trace_background) gray90
  68set colors(trace_foreground) black
  69
  70set typecode(0) VECTOR
  71set typecode(1) SYMBOL
  72set typecode(66) STRING
  73set typecode(3) PAIR
  74set typecode(36) CLOSURE
  75set typecode(85) FLONUM
  76set typecode(39) PORT
  77set typecode(8) STRUCTURE
  78set typecode(41) POINTER
  79set typecode(42) LOCATIVE
  80set typecode(43) TAGGED_POINTER
  81set typecode(77) LAMBDA_INFO
  82set typecode(15) BUCKET
  83
  84set EXEC_EVENT_MASK 32; # signal
  85set STEP_EVENT_MASK 54; # call, entry, assign, signal
  86
  87set membar_height 50
  88set value_cutoff_limit 200; # must be lower than limit in dbg-stub.c
  89
  90set the_name "feathers"
  91set header_text "$the_name - (c)MMXV The CHICKEN Team - Version $version"
  92set startup_file ".$the_name"
  93
  94set client_addr ""
  95set client_file ""
  96set current_filename ""
  97set current_c_filename ""
  98set file_list {}
  99set bp_queue {}
 100set watched_queue {}
 101set value_queue {}
 102set current_line ""
 103set current_c_line ""
 104set current_bp_lines {}
 105set current_bp_globals {}
 106set font_name "Courier"
 107set font_size 12
 108set program_name ""
 109set search_path {"."}
 110set data_view ""
 111set c_view ""
 112set data_queue {}
 113set reply_queue {}
 114set starting_up 1
 115set stepping 0
 116set terminated 0
 117set arguments_item_id ""
 118set watched_variables {}
 119set current_variable ""
 120set current_value ""
 121set listening 0
 122set process_id 0
 123set statistics_data ""
 124set mark_start_index(.t) ""
 125set mark_start_index(.code.t) ""
 126set current_c_location ""
 127set last_marked_widget .t
 128set selected_filename ""
 129set trace_data ""
 130set last_location ""
 131set logging 0
 132
 133set env(CHICKEN_DEBUGGER) "localhost:9999"
 134
 135
 136proc Log {msg} {
 137    global logging
 138
 139    if {$logging} {puts stderr $msg}
 140}
 141
 142
 143proc SetupGUI {} {
 144    global font_name font_size colors the_name selected_filename
 145    label .h -height 1 -textvariable header_text -anchor w
 146    scrollbar .s -command {.t yview}
 147    text .t -wrap char -yscrollcommand {.s set} -cursor arrow -state disabled \
 148        -font [list $font_name $font_size]
 149    frame .f
 150    ttk::combobox .files -postcommand FilesList -textvariable selected_filename
 151    pack .h -side top -fill x
 152    pack .files -side top -fill x
 153    pack .f -side bottom -fill x
 154    pack .s -fill y -side right
 155    pack .t -fill both -expand 1
 156
 157    for {set i 1} {$i <= 10} {incr i} {
 158        button .f.b$i -text "F$i" -font {Helvetica 10} -borderwidth 0 -relief flat \
 159            -pady 0
 160        pack .f.b$i -side left -expand 1 -fill x
 161    }
 162
 163    .f.b1 configure -text "F1 Run"
 164    .f.b2 configure -text "F2 Where"
 165    .f.b3 configure -text "F3 AddDir"
 166    .f.b4 configure -text "F4 Data"
 167    .f.b5 configure -text "F5 Continue"
 168    .f.b6 configure -text "F6 Step"
 169    .f.b7 configure -text "F7 Find Prev"
 170    .f.b8 configure -text "F8 Find Next"
 171    .f.b9 configure -text "F9 C"
 172    .f.b10 configure -text "F10 Exit"
 173    .h configure -background $colors(header_background) \
 174        -foreground $colors(header_foreground) -font {Helvetica 12} \
 175        -borderwidth 0
 176    .t configure -background $colors(text_background) \
 177        -foreground $colors(text_foreground) \
 178        -insertbackground $colors(text_foreground) -borderwidth 0
 179
 180    .t tag configure ev -background $colors(event_background) \
 181        -foreground $colors(event_foreground)
 182    .t tag configure bp -background $colors(breakpoint_background) \
 183        -foreground $colors(breakpoint_foreground)
 184    .t tag configure hl -background $colors(highlight_background) \
 185        -foreground $colors(highlight_foreground)
 186    .t tag configure mk -background $colors(mark_background) \
 187        -foreground $colors(mark_foreground)
 188    .t tag lower mk sel
 189    .t tag lower bp mk
 190    .t tag lower hl bp
 191    .t tag lower ev hl
 192    focus .t
 193    wm title . $the_name
 194}
 195
 196
 197proc SetupBindings {} {
 198    for {set i 1} {$i <= 10} {incr i} {
 199        bind . <F$i> [list .f.b$i invoke]
 200    }
 201
 202    .f.b1 configure -command RunProcess
 203    .f.b2 configure -command LocateFocus
 204    .f.b3 configure -command AddDirectory
 205    .f.b4 configure -command ShowData
 206    .f.b5 configure -command ContinueExecution
 207    .f.b6 configure -command StepExecution
 208    .f.b7 configure -command FindPrevious
 209    .f.b8 configure -command FindNext
 210    .f.b9 configure -command OpenCView
 211    .f.b10 configure -command Terminate
 212    bind .t <ButtonPress-1> { focus .t; ToggleBreakpoint %y; break }
 213    bind .t <ButtonRelease-1> break
 214    bind .t <space> {StepExecution; break}
 215    bind .t <Return> {ToggleBreakpoint; break}
 216    bind .t <Up> {MoveFocus -1; break}
 217    bind .t <Down> {MoveFocus 1; break}
 218    bind .t <plus> {ResizeFont 1; break}
 219    bind .t <minus> {ResizeFont -1; break}
 220    bind .t <Escape> {Interrupt}
 221    bind .t <ButtonPress-3> {StartMark %W %x %y; break}
 222    bind .t <Motion> {MoveMark %W %x %y; break}
 223    bind .t <ButtonRelease-3> {EndMark %W; break}
 224    bind .files <<ComboboxSelected>> {SelectFile; focus .t}
 225    wm protocol . WM_DELETE_WINDOW Terminate
 226}
 227
 228
 229proc SetupDataView {} {
 230    global colors arguments_item_id stats membar_height the_name font_name font_size
 231    toplevel .data
 232    ttk::treeview .data.t -yscrollcommand {.data.s set} -columns {Values Addresses} \
 233        -selectmode browse
 234    .data.t heading 0 -text Value
 235    .data.t heading 1 -text Address
 236    scrollbar .data.s -command {.data.t yview}
 237    entry .data.e
 238    canvas .data.c -height $membar_height
 239    frame .data.f
 240    text .data.f.tr -state disabled -yscrollcommand {.data.f.trs set} -height 20 \
 241        -font [list $font_name $font_size] -foreground $colors(trace_foreground) \
 242        -background $colors(trace_background)
 243    scrollbar .data.f.trs -command {.data.f.tr yview}
 244    pack .data.f -side bottom -fill x
 245    pack .data.c -side bottom -fill x
 246    pack .data.e -side bottom -fill x
 247    pack .data.s -fill y -side right
 248    pack .data.t -fill both -expand 1
 249    pack .data.f.trs -fill y -side right
 250    pack .data.f.tr -side bottom -fill both -expand 1
 251    .data.t tag configure watched -foreground $colors(breakpoint_foreground) \
 252        -background $colors(breakpoint_background)
 253    set arguments_item_id [.data.t insert {} end -text "<arguments>"]
 254    set stats(fromspace_used) [.data.c create rectangle 0 0 0 0 -fill gray80]
 255    set stats(fromspace_unused) [.data.c create rectangle 0 0 0 0 -fill gray40]
 256    set stats(scratchspace_used) [.data.c create rectangle 0 0 0 0 -fill gray80]
 257    set stats(scratchspace_unused) [.data.c create rectangle 0 0 0 0 -fill gray40]
 258    set stats(nursery_used) [.data.c create rectangle 0 0 0 0 -fill gray80]
 259    set stats(nursery_unused) [.data.c create rectangle 0 0 0 0 -fill gray40]
 260    set mh [expr $membar_height / 3]
 261    set stats(fromspace_name) [.data.c create text 10 0 -anchor nw -text "heap"]
 262    set stats(scratchspace_name) [.data.c create text 10 $mh -anchor nw -text \
 263        "scratch"]
 264    set stats(nursery_name) [.data.c create text 10 [expr $mh * 2] -anchor nw \
 265        -text "nursery"]
 266    set stats(fromspace_percentage) [.data.c create text 0 0 -anchor center]
 267    set stats(scratchspace_percentage) [.data.c create text 0 0 -anchor center]
 268    set stats(nursery_percentage) [.data.c create text 0 0 -anchor center]
 269    set stats(fromspace_size) [.data.c create text 0 0 -anchor ne]
 270    set stats(scratchspace_size) [.data.c create text 0 0 -anchor ne]
 271    set stats(nursery_size) [.data.c create text 0 0 -anchor ne]
 272    wm title .data "$the_name - data view"
 273}
 274
 275
 276proc SetupDataViewBindings {} {
 277    bind .data <F3> AddDirectory
 278    bind .data <F4> ShowData
 279    bind .data <F5> ContinueExecution
 280    bind .data <F6> StepExecution
 281    bind .data <F10> Terminate
 282    bind .data.e <Return> {WatchGlobal; break}
 283    bind .data.t <BackSpace> {RemoveGlobal; break}
 284    bind .data.t <Delete> {RemoveGlobal; break}
 285    bind .data.t <<TreeviewOpen>> OpenDataItem
 286    bind .data.t <Return> {ToggleVariableWatch; break}
 287    bind .data.t <Double-Button-1> {ToggleVariableWatch %x %y; break}
 288    bind .data.t <<TreeviewSelect>> {Log [.data.t focus]; break}
 289    bind .data.c <Configure> {RedrawStatistics}
 290    wm protocol .data WM_DELETE_WINDOW CloseDataView
 291}
 292
 293
 294proc SetupCView {} {
 295    global font_name font_size colors the_name
 296    toplevel .code
 297    label .code.h -height 1 -text "" -anchor w
 298    scrollbar .code.s -command {.code.t yview}
 299    text .code.t -wrap char -yscrollcommand {.code.s set} -cursor arrow -state \
 300        disabled -font [list $font_name $font_size]
 301    frame .code.f
 302    pack .code.h -side top -fill x
 303    pack .code.s -fill y -side right
 304    pack .code.f -fill x -side bottom
 305    pack .code.t -fill both -expand 1
 306    .code.h configure -background $colors(header_background) \
 307        -foreground $colors(header_foreground) -font {Helvetica 12} \
 308        -borderwidth 0
 309    .code.t configure -background $colors(text_background) \
 310        -foreground $colors(text_foreground) \
 311        -insertbackground $colors(text_foreground) -borderwidth 0
 312    .code.t tag configure hl -background $colors(highlight_background) \
 313        -foreground $colors(highlight_foreground)
 314    .code.t tag configure mk -background $colors(mark_background) \
 315        -foreground $colors(mark_foreground)
 316    .code.t tag lower mk sel
 317    .code.t tag lower hl mk
 318    wm title .code "$the_name - code view"
 319    focus .code.t
 320}
 321
 322
 323proc SetupCViewBindings {} {
 324    bind .code <F3> AddDirectory
 325    bind .code <F4> ShowData
 326    bind .code <F5> ContinueExecution
 327    bind .code <F6> StepExecution
 328    bind .code <F7> {FindPrevious .code.t}
 329    bind .code <F8> {FindNext .code.t}
 330    bind .code <F10> Terminate
 331    bind .code.t <ButtonPress-1> {focus .code.t}
 332    bind .code.t <ButtonRelease-1> break
 333    bind .code <plus> {ResizeFont 1; break}
 334    bind .code <minus> {ResizeFont -1; break}
 335    bind .code.t <ButtonPress-3> {StartMark %W %x %y; break}
 336    bind .code.t <Motion> {MoveMark %W %x %y; break}
 337    bind .code.t <ButtonRelease-3> {EndMark %W; break}
 338    wm protocol .code WM_DELETE_WINDOW CloseCView
 339}
 340
 341
 342proc FilesList {} {
 343    global file_list
 344    .files configure -values $file_list
 345}
 346
 347
 348proc CloseDataView {} {
 349    global data_view
 350
 351    if {$data_view != ""} {
 352        set data_view ""
 353        destroy .data
 354    }
 355}
 356
 357
 358proc CloseCView {} {
 359    global c_view
 360
 361    if {$c_view != ""} {
 362        set c_view ""
 363        destroy .code
 364    }
 365}
 366
 367
 368proc ShowData {} {
 369    global data_view starting_up client_file program_name the_name
 370
 371    if {$data_view == ""} {
 372        SetupDataView
 373        SetupDataViewBindings
 374        set data_view .data
 375        wm title .data "$the_name - $program_name - data view"
 376
 377        if {!$starting_up && $client_file != ""} UpdateData
 378    }
 379}
 380
 381
 382proc OpenCView {} {
 383    global c_view starting_up current_c_location the_name program_name
 384
 385    if {$c_view == ""} {
 386        SetupCView
 387        SetupCViewBindings
 388        set c_view .code
 389        wm title .code "$the_name - $program_name - code view"
 390
 391        Log "$current_c_location"
 392
 393        if {$current_c_location != ""} {
 394            LocateCSource $current_c_location
 395        }
 396    }
 397}
 398
 399
 400proc AddDirectory {} {
 401    global search_path current_filename
 402    set dir "."
 403
 404    if {$current_filename != ""} {
 405        set dir [file dirname $current_filename]
 406    }
 407
 408    set dir [tk_chooseDirectory -title "Select directory to add to search path" \
 409        -initialdir $dir]
 410
 411    if {$dir != ""} {
 412        lappend search_path $dir
 413    }
 414}
 415
 416
 417proc ResizeFont {n} {
 418    global font_size font_name c_view
 419    incr font_size $n
 420    .t configure -font [list $font_name $font_size]
 421
 422    if {$c_view != ""} {
 423        .code.t configure -font [list $font_name $font_size]
 424    }
 425}
 426
 427
 428proc Flash {{color red}} {
 429    global colors
 430    .t configure -background $color
 431    update
 432    after 100 {.t configure -background $colors(text_background)}
 433}
 434
 435
 436proc CheckListening {} {
 437    global listening
 438
 439    if {!$listening} {
 440        Flash
 441        return 0
 442    }
 443
 444    return 1
 445}
 446
 447
 448proc MoveFocus {amount} {
 449    global current_line
 450    set ln [expr $current_line + $amount]
 451    SetFocus $ln
 452}
 453
 454
 455proc LocateFocus {} {
 456    global last_location
 457
 458    if {$last_location != ""} {
 459        SetFocus $last_location
 460    }
 461}
 462
 463
 464proc SetFocus {line} {
 465    global current_line
 466
 467    if {$line > 0 && $line <= [.t count -lines 1.0 end]} {
 468        set old [.t tag ranges hl]
 469
 470        if {$old != ""} {
 471            eval .t tag remove hl $old
 472        }
 473
 474        set current_line $line
 475        .t tag add hl $line.0 "$line.0 lineend + 1 chars"
 476        .t see $line.0
 477    }
 478}
 479
 480
 481proc SetCFocus {line} {
 482    global current_c_line
 483
 484    if {$line > 0 && $line <= [.code.t count -lines 1.0 end]} {
 485        set old [.code.t tag ranges hl]
 486
 487        if {$old != ""} {
 488            eval .code.t tag remove hl $old
 489        }
 490
 491        set current_c_line $line
 492        .code.t tag add hl $line.0 "$line.0 lineend + 1 chars"
 493        .code.t see $line.0
 494    }
 495}
 496
 497
 498proc Interrupt {} {
 499    global process_id listening
 500
 501    if {$listening || $process_id == 0} return
 502
 503    catch {exec kill -USR2 $process_id}
 504}
 505
 506
 507proc ToggleBreakpoint {{y ""}} {
 508    global current_filename bp_queue current_bp_lines
 509    global current_line client_file reply_queue
 510
 511    if {$client_file == ""} return
 512
 513    if {$y != ""} {
 514        if {[catch {set p [.t index @1,$y]}]} return
 515
 516        if {![regexp {^(\d+)\.} $p _ line]} return
 517    } else {
 518        set line $current_line
 519    }
 520
 521    set aname "file:$current_filename"
 522    global $aname
 523    set aref "$aname\($line\)"
 524
 525    if {![CheckListening]} return
 526
 527    if {[info exists $aref]} {
 528        set bps [set $aref]
 529
 530        if {$bps != ""} {
 531            set bp1 [lindex $bps 0]
 532            set bprest [lrange $bps 1 end]
 533            set bp_queue [concat $bp_queue $bprest]
 534
 535            if {[lsearch -exact $current_bp_lines $line] != -1} {
 536                UnmarkBP $line
 537                SendReply CLEAR_BREAKPOINT $bp1
 538                lappend reply_queue RemoveBPReply
 539            } else {
 540                MarkBP $line
 541                SendReply SET_BREAKPOINT $bp1
 542                lappend reply_queue AddBPReply
 543            }
 544        }
 545    }
 546}
 547
 548
 549proc ToggleVariableWatch {{x ""} {y ""}} {
 550    global globals current_bp_globals bp_queue
 551
 552    if {![CheckListening]} return
 553
 554    if {$x == ""} {
 555        set item [.data.t focus]
 556    } else {
 557        if {[catch {.data.t identify item $x $y} item]} return
 558    }
 559
 560    if {$item == ""} return
 561
 562    if {[.data.t parent $item] != ""} return
 563
 564    set name [.data.t item $item -text]
 565
 566    if {$name == "<arguments>"} return
 567
 568    if {![info exists globals($name)]} return
 569
 570    Log "globals: $name -> $globals($name)"
 571
 572    set bps $globals($name)
 573
 574    if {$bps != ""} {
 575        set bp1 [lindex $bps 0]
 576        set bprest [lrange $bps 1 end]
 577        set bp_queue [concat $bp_queue $bprest]
 578
 579        if {[lsearch -exact $current_bp_globals $item] != -1} {
 580            UnmarkWatchedVariable $item
 581            SendReply CLEAR_BREAKPOINT $bp1
 582            lappend reply_queue RemoveBPReply
 583        } else {
 584            MarkWatchedVariable $item
 585            SendReply SET_BREAKPOINT $bp1
 586            lappend reply_queue AddBPReply
 587        }
 588    }
 589}
 590
 591
 592proc AddBPReply {} {
 593    global bp_queue reply_queue
 594
 595    if {$bp_queue != ""} {
 596        set bp1 [lindex $bp_queue 0]
 597        set bp_queue [lrange $bp_queue 1 end]
 598        SendReply SET_BREAKPOINT $bp1
 599
 600        if {$bp_queue != ""} {
 601            lappend reply_queue AddBPReply
 602        }
 603    }
 604}
 605
 606
 607proc RemoveBPReply {} {
 608    global bp_queue reply_queue
 609
 610    if {$bp_queue != ""} {
 611        set bp1 [lindex $bp_queue 0]
 612        set bp_queue [lrange $bp_queue 1 end]
 613        SendReply CLEAR_BREAKPOINT $bp1
 614
 615        if {$bp_queue != ""} {
 616            lappend reply_queue RemoveBPReply
 617        }
 618    }
 619}
 620
 621
 622proc MarkBP {line} {
 623    global current_bp_lines
 624
 625    if {[lsearch -exact $current_bp_lines $line] == -1} {
 626        .t tag add bp $line.0 "$line.0 lineend"
 627        lappend current_bp_lines $line
 628    }
 629}
 630
 631
 632proc UnmarkBP {line} {
 633    global current_bp_lines
 634    set i [lsearch -exact $current_bp_lines $line]
 635
 636    if {$i != -1} {
 637        set current_bp_lines [lreplace $current_bp_lines $i $i]
 638        .t tag remove bp $line.0 "$line.0 lineend"
 639    }
 640}
 641
 642
 643proc MarkWatchedVariable {item} {
 644    global current_bp_globals
 645
 646    if {[lsearch -exact $current_bp_globals $item] == -1} {
 647        .data.t tag add watched $item
 648        lappend current_bp_globals $item
 649    }
 650}
 651
 652
 653proc UnmarkWatchedVariable {item} {
 654    global current_bp_globals
 655    set i [lsearch -exact $current_bp_globals $item]
 656
 657    if {$i != -1} {
 658        set current_bp_globals [lreplace $current_bp_globals $i $i]
 659        .data.t tag remove watched $item
 660    }
 661}
 662
 663
 664proc Terminate {} {
 665    global client_file process_id
 666
 667    if {$client_file != ""} {
 668        SendReply TERMINATE
 669        set f $client_file
 670        set client_file ""
 671        close $f
 672        catch {exec kill -9 $process_id}
 673    }
 674
 675    exit
 676}
 677
 678
 679proc RunProcess {{prg ""}} {
 680    global env client_file program_name search_path reply_queue current_filename
 681    global data_queue bp_queue starting_up stepping terminated current_bp_lines
 682    global terminated watched_variables watched_queue listening file_list
 683    global value_queue process_id current_bp_globals data_view statistics_data
 684    global arguments_item_id trace_data last_location
 685
 686    if {$client_file != ""} {
 687        if {!$terminated} {SendReply TERMINATE}
 688
 689        set f $client_file
 690        set client_file ""
 691        close $f
 692    }
 693
 694    set program_name $prg
 695
 696    if {$program_name == ""} {
 697        set program_name [tk_getOpenFile -title "Select executable"]
 698    }
 699
 700    if {$program_name == ""} return
 701
 702    set args [lassign $program_name prgfname]
 703    set prgfname [file normalize $prgfname]
 704
 705    if {![file exists $prgfname]} {
 706        .t configure -state normal
 707        .t insert end "Could not start program:\n\nfile `$prgfname' does not exist"
 708        .t see end
 709        .t configure -state disabled
 710    }
 711
 712    lappend search_path [file dirname $prgfname]
 713    set reply_queue {}
 714    set data_queue {}
 715    set bp_queue {}
 716    set watched_queue {}
 717    set value_queue {}
 718    set last_location ""
 719    set starting_up 1
 720    set stepping 0
 721    set terminated 0
 722    set current_bp_lines {}
 723    set current_bp_globals {}
 724    set current_filename ""
 725    set watched_variables {}
 726    set listening 0
 727    set process_id 0
 728    set statistics_data ""
 729    set file_list {}
 730    set trace_data ""
 731    .t configure -state normal
 732    .t delete 1.0 end
 733    .t configure -state disabled
 734
 735    if {$data_view != ""} {
 736        .data.t delete [lrange [.data.t children {}] 1 end]
 737        .data.t delete [.data.t children $arguments_item_id]
 738    }
 739
 740    if {[catch {eval exec $prgfname {*}$args <@ stdin >@ stdout 2>@ stderr &} result]} {
 741        .t configure -state normal
 742        .t insert end "Could not start program:\n\n$result"
 743        .t see end
 744        .t configure -state disabled
 745    } else {
 746        set process_id $result
 747    }
 748}
 749
 750
 751proc UpdateHeader {{msg ""}} {
 752    global header_text current_filename client_addr current_line
 753    set header_text $client_addr
 754
 755    if {$current_filename != ""} {
 756        set header_text $current_filename
 757
 758        if {$current_line != ""} {
 759            append header_text ":$current_line"
 760        }
 761    }
 762
 763    if {$msg != ""} {
 764        append header_text " - $msg"
 765    }
 766}
 767
 768
 769proc ProcessInput {} {
 770    global client_file terminated
 771    gets $client_file line
 772
 773    if {[eof $client_file]} {
 774        close $client_file
 775        set client_file ""
 776        set terminated 1
 777        UpdateHeader "connection closed"
 778    } elseif {![fblocked $client_file]} {
 779        Log "Input: $line"
 780        ProcessLine $line
 781    }
 782}
 783
 784
 785proc ProcessLine {line} {
 786    if {[regexp {^\((\d+)\s+([^\s]*)\s+([^\s]*)\s+([^)]*)\)$} $line _ evt loc val cloc]} {
 787        set val [ProcessString $val]
 788        set loc [ProcessString $loc]
 789        set cloc [ProcessString $cloc]
 790        ProcessEvent $evt $loc $val $cloc
 791    } elseif {[regexp {^\(\*\s*(.*)\)$} $line _ data]} {
 792        ProcessData $data
 793    } else {
 794        UpdateHeader "invalid input: [string range $line 0 40]..."
 795    }
 796}
 797
 798
 799proc ProcessEvent {evt loc val cloc} {
 800    global events reply_queue starting_up EXEC_EVENT_MASK data_queue c_view
 801    global STEP_EVENT_MASK stepping data_view listening value_queue statistics_data
 802    global current_c_location protocol_version the_name program_name trace_data
 803
 804    set listening 1
 805
 806    if {[info exists events($evt)]} {
 807        set eventname $events($evt)
 808    } else {
 809        UpdateHeader "unrecognized event: $evt"
 810        return
 811    }
 812
 813    if {$data_queue != ""} {
 814        set data_queue [lrange $data_queue 1 end]
 815    }
 816
 817    Log "evt: $eventname, dq: $data_queue, rq: $reply_queue, vq: $value_queue"
 818
 819    if {$eventname != "listen"} {
 820        set statistics_data ""
 821        set trace_data ""
 822    }
 823
 824    set current_c_location $cloc
 825
 826    if {$c_view != ""} {
 827        LocateCSource $cloc
 828    }
 829
 830    switch $eventname {
 831        connect {
 832            if {![regexp {^([^:]+):([^:]+):(\d+)$} $loc _ name pid pv]} {
 833                UpdateHeader "invalid connection info: $loc"
 834                return
 835            }
 836
 837            if {$pv > $protocol_version} {
 838                UpdateHeader "client protocol doesn't match: $pv"
 839                return
 840            }
 841
 842            wm title . "$the_name - $program_name"
 843
 844            Log "\n##################### CONNECT ##################"
 845            SendReply SETMASK $STEP_EVENT_MASK
 846            set stepping 1
 847            lappend reply_queue FetchEventListReply FirstStepReply
 848        }
 849        listen {
 850            if {$reply_queue != ""} {
 851                set action [lindex $reply_queue 0]
 852                set reply_queue [lrange $reply_queue 1 end]
 853                Log "action: $action"
 854                $action
 855            } elseif {$val == 1} {
 856                # new dbg-info was registered
 857                lappend reply_queue ApplyTags
 858                FetchEventListReply
 859            }
 860        }
 861        default {
 862            # call/entry/assign/signal/gc
 863            LocateEvent $loc $val
 864            UpdateHeader "\[$eventname\]"
 865
 866            if {$starting_up} {
 867                SendReply SETMASK $EXEC_EVENT_MASK
 868                set starting_up 0
 869            } elseif {$data_view != ""} UpdateData
 870        }
 871    }
 872}
 873
 874
 875proc UpdateData {} {
 876    global data_queue reply_queue watched_variables
 877    global watched_queue
 878    set watched_queue $watched_variables
 879    lappend reply_queue GetGlobals
 880    lappend data_queue GetAVData
 881    SendReply GET_AV
 882}
 883
 884
 885proc GetAVData {data} {
 886    global arguments_item_id value_queue
 887    set vals [ParseValueList $data]
 888    set cs [.data.t children $arguments_item_id]
 889    set len [llength $vals]
 890    set clen [llength $cs]
 891
 892    for {set i 0} {$i < $len} {incr i} {
 893        lassign [ValueData [lindex $vals $i]] type text addr
 894
 895        if {$i >= $clen} {
 896            set c [.data.t insert $arguments_item_id end -text $type -values \
 897                [list $text $addr]]
 898        } else {
 899            set c [lindex $cs $i]
 900            .data.t item $c -text $type -values [list $text $addr]
 901        }
 902
 903        if {$addr != ""} {
 904            lappend value_queue $c
 905        }
 906
 907        incr i
 908    }
 909
 910    if {$i < $clen} {
 911        .data.t delete [lrange $cs $i end]
 912    }
 913
 914    .data.t item $arguments_item_id -open 1
 915}
 916
 917
 918proc GetGlobals {} {
 919    global data_queue reply_queue watched_queue current_variable
 920    global data_view value_queue
 921
 922    if {$watched_queue != ""} {
 923        set current_variable [lindex $watched_queue 0]
 924        set watched_queue [lrange $watched_queue 1 end]
 925        lappend data_queue GetGlobalData
 926        set name [MangleSymbol [.data.t item $current_variable -text]]
 927        SendReply GET_GLOBAL "\"$name\""
 928        lappend reply_queue GetGlobals
 929    } elseif {$data_view != ""} {
 930        if {$value_queue != ""} {
 931            GetValues
 932        } else {
 933            GetStatistics
 934        }
 935    }
 936}
 937
 938
 939proc GetValues {} {
 940    global data_view value_queue current_value data_queue reply_queue
 941
 942    if {$data_view != ""} {
 943        if {$value_queue != ""} {
 944            set current_value [lindex $value_queue 0]
 945            Log "get value: $current_value"
 946            set value_queue [lrange $value_queue 1 end]
 947            lappend data_queue GetValueData
 948            scan [.data.t set $current_value 1] %x addr
 949            SendReply GET_SLOTS $addr
 950            lappend reply_queue GetValues
 951        } else {
 952            UpdateValueText {}
 953            GetTrace
 954        }
 955    }
 956}
 957
 958
 959proc GetTrace {} {
 960    global data_queue trace_data reply_queue
 961
 962    if {$trace_data == ""} {
 963        lappend reply_queue GetStatistics
 964        lappend data_queue GetTraceData
 965        SendReply GET_TRACE
 966    } else GetStatistics
 967}
 968
 969
 970proc GetTraceData {data} {
 971    global trace_data
 972
 973    if {![regexp {^"([^"]*)"$} $data _ str]} {
 974        append trace_data "<invalid trace data>\n"
 975    } else {
 976        append trace_data "$str\n"
 977    }
 978}
 979
 980
 981proc RedrawTrace {} {
 982    global trace_data
 983    .data.f.tr configure -state normal
 984    .data.f.tr delete 1.0 end
 985    .data.f.tr insert 1.0 $trace_data
 986    .data.f.tr configure -state disabled
 987}
 988
 989
 990proc GetStatistics {} {
 991    global data_queue statistics_data reply_queue trace_data
 992
 993    if {$trace_data != ""} RedrawTrace
 994
 995    if {$statistics_data == ""} {
 996        lappend data_queue GetStatisticsData
 997        SendReply GET_STATS
 998    }
 999}
 1000
1001
1002proc GetStatisticsData {data} {
1003    global statistics_data
1004    set addrs [ParseValueList $data]
1005    set statistics_data $addrs
1006    RedrawStatistics
1007}
1008
1009
1010proc RedrawStatistics {} {
1011    global statistics_data stats membar_height
1012
1013    if {$statistics_data == ""} return
1014
1015    set w [winfo width .data.c]
1016    set w2 [expr $w / 2]
1017    set w10 [expr $w - 10]
1018    set mh [expr $membar_height / 3]
1019    set mh2 [expr $mh * 2]
1020
1021    # fromspace
1022    lassign [CalcSize [lindex $statistics_data 0] [lindex $statistics_data 1] \
1023        [lindex $statistics_data 6] $w] p pc sz
1024    .data.c coords $stats(fromspace_used) 0 0 $p $mh
1025    .data.c coords $stats(fromspace_unused) $p 0 $w $mh
1026    .data.c coords $stats(fromspace_percentage) $w2 [expr $mh / 2]
1027    .data.c itemconfigure $stats(fromspace_percentage) -text "$pc%"
1028    .data.c coords $stats(fromspace_size) $w10 0
1029    .data.c itemconfigure $stats(fromspace_size) -text "${sz}k"
1030
1031    # scratchspace
1032    if {[lindex $statistics_data 2] != 0} {
1033        lassign [CalcSize [lindex $statistics_data 2] [lindex $statistics_data 3] \
1034            [lindex $statistics_data 7] $w] p pc sz
1035        .data.c coords $stats(scratchspace_used) 0 $mh $p $mh2
1036        .data.c coords $stats(scratchspace_unused) $p $mh $w $mh2
1037        .data.c coords $stats(scratchspace_percentage) $w2 [expr int($mh * 1.5)]
1038        .data.c itemconfigure $stats(scratchspace_percentage) -text "$pc%"
1039        .data.c coords $stats(scratchspace_size) $w10 $mh
1040        .data.c itemconfigure $stats(scratchspace_size) -text "${sz}k"
1041    }
1042
1043    # nursery
1044    lassign [CalcSize [lindex $statistics_data 4] [lindex $statistics_data 5] \
1045        [lindex $statistics_data 8] $w 1] p pc sz
1046    .data.c coords $stats(nursery_used) 0 $mh2 $p $membar_height
1047    .data.c coords $stats(nursery_unused) $p $mh2 $w $membar_height
1048    .data.c coords $stats(nursery_percentage) $w2 [expr int($mh * 2.5)]
1049    .data.c itemconfigure $stats(nursery_percentage) -text "$pc%"
1050    .data.c coords $stats(nursery_size) $w10 $mh2
1051    .data.c itemconfigure $stats(nursery_size) -text "${sz}k"
1052}
1053
1054
1055proc CalcSize {start limit top width {inv 0}} {
1056    set total [expr $limit - $start]
1057
1058    if {$inv} {
1059        set amount [expr $limit - $top]
1060    } else {
1061        set amount [expr $top - $start]
1062    }
1063
1064    set p [expr int(double($amount) / $total * 100)]
1065    set sz [expr $total / 1024]
1066    return [list [expr int((double($width) / $total) * $amount)] $p $sz]
1067}
1068
1069
1070proc GetValueData {data} {
1071    global current_value value_queue typecode value_cutoff_limit
1072
1073    set vals [ParseValueList $data]
1074    set bits [lindex $vals 1]
1075
1076    if {[info exists typecode($bits)]} {
1077        set type $typecode($bits)
1078    } else {
1079        set type "<invalid: $bits>"
1080    }
1081
1082    .data.t item $current_value -text $type
1083    set cs {}
1084
1085    switch [lindex $vals 0] {
1086        "SPECIAL" {
1087            set cs [.data.t children $current_value]
1088
1089            if {$cs == ""} {
1090                set c1 [.data.t insert $current_value end]
1091                set cs {}
1092            } else {
1093                set c1 [lindex $cs 0]
1094                set cs [lrange $cs 1 end]
1095            }
1096
1097            .data.t item $c1 -text "<native pointer>" -values \
1098                [list "" [format 0x%x [lindex $vals 2]]]
1099            set vals [lrange $vals 3 end]
1100        }
1101        "VECTOR" {
1102            set vals [lrange $vals 2 end]
1103            set cs [.data.t children $current_value]
1104        }
1105        "BLOB" {
1106            if {$type == "STRING" || $type == "LAMBDA_INFO"} {
1107                set str "\""
1108
1109                foreach c [lrange $vals 2 end] {
1110                    # XXX escape special chars
1111                    append str [format %c $c]
1112                }
1113
1114                append str "\""
1115            } elseif {$type == "FLONUM"} {
1116                set bytes [binary format c* $vals]
1117                binary scan $bytes d str
1118            } else {
1119                set str "#\${"
1120
1121                foreach c [lrange $vals 2 end] {
1122                    append str [format %02x $c]
1123                }
1124
1125                append str "}"
1126            }
1127
1128            .data.t set $current_value 0 $str
1129            set cs [.data.t children $current_value]
1130
1131            if {$cs != ""} {.data.t delete $cs}
1132
1133            return
1134        }
1135        default {
1136            UpdateHeader "invalid value: $data"
1137        }
1138    }
1139
1140    set vlen [llength $vals]
1141    set clen [llength $cs]
1142
1143    for {set i 0} {$i < $vlen} {incr i} {
1144        set val [lindex $vals $i]
1145        lassign [ValueData $val] type text addr
1146
1147        if {$i >= $clen} {
1148            set c [.data.t insert $current_value end -text $type -values \
1149                [list $text $addr]]
1150            Log "insert: $c"
1151        } else {
1152            set c [lindex $cs $i]
1153            Log "reuse: $c"
1154            .data.t item $c -text $type -values [list $text $addr]
1155        }
1156
1157        if {$i >= $value_cutoff_limit} {
1158            .data.t item $c -text ":" -values {"" ""}
1159            incr i
1160            break
1161        }
1162
1163        if {$addr != ""} {
1164            if {[.data.t item [.data.t parent $c] -open]} {
1165                lappend value_queue $c
1166            }
1167        } else {
1168            .data.t delete [.data.t children $c]
1169        }
1170    }
1171
1172    if {$i < $clen} {
1173        .data.t delete [lrange $cs $i end]
1174    }
1175}
1176
1177
1178proc UpdateValueText {node} {
1179    global value_cutoff_limit
1180    set cs [.data.t children $node]
1181
1182    foreach c $cs {
1183        UpdateValueText $c
1184    }
1185
1186    if {$node == ""} return
1187
1188    set addr [.data.t set $node 1]
1189
1190    if {$addr == ""} return
1191
1192    set type [.data.t item $node -text]
1193
1194    if {$type == ":"} return
1195
1196    set str "..."
1197
1198    switch $type {
1199        "" return
1200        "<arguments>" return
1201        "<native pointer>" return
1202        FLONUM return
1203        LAMBDA_INFO return
1204        STRING return
1205        PAIR {
1206            set car [.data.t set [lindex $cs 0] 0]
1207            set cdr [.data.t set [lindex $cs 1] 0]
1208            set str "($car"
1209
1210            switch [.data.t item [lindex $cs 1] -text] {
1211                NULL {append str ")"}
1212                PAIR {append str " [string range $cdr 1 end]"}
1213                default {append str " . $cdr)"}
1214            }
1215        }
1216        VECTOR {
1217            if {$cs == ""} {
1218                set str "#()"
1219            } else {
1220                set x0 [.data.t set [lindex $cs 0] 0]
1221                set str "#($x0"
1222
1223                foreach c [lrange $cs 1 end] {
1224                    set x [.data.t set $c 0]
1225                    append str " $x"
1226                }
1227
1228                append str ")"
1229            }
1230        }
1231        SYMBOL {
1232            set name [.data.t set [lindex $cs 1] 0]
1233            set str [DemangleSymbol [string range $name 1 "end-1"]]
1234        }
1235        default {
1236            set str "#<$type $addr>"
1237        }
1238    }
1239
1240    if {[string length $str] >= $value_cutoff_limit} {
1241        set str "[string range $str 0 $value_cutoff_limit]..."
1242     }
1243
1244    .data.t set $node 0 $str
1245}
1246
1247
1248proc OpenDataItem {} {
1249    global value_queue listening
1250    set item [.data.t focus]
1251
1252    if {$item == ""} return
1253
1254    if {!$listening} return
1255
1256    if {[.data.t parent $item] == ""} return
1257
1258    set cs [.data.t children $item]
1259
1260    foreach c $cs {
1261        if {[.data.t set $c 1] != "" && \
1262            [.data.t item $c -text] != "<native pointer>"} {
1263            lappend value_queue $c
1264        }
1265    }
1266
1267    GetValues
1268}
1269
1270
1271proc WatchGlobal {} {
1272    global data_queue watched_variables current_variable reply_queue
1273
1274    if {![CheckListening]} return
1275
1276    set name [string trim [.data.e get]]
1277    .data.e delete 0 end
1278
1279    if {$name == ""} return
1280
1281    if {[lsearch -exact $watched_variables $name] != -1} return
1282
1283    set id [.data.t insert {} end -text $name]
1284    lappend watched_variables $id
1285    lappend data_queue GetGlobalData
1286    set current_variable $id
1287    set name [MangleSymbol $name]
1288    SendReply GET_GLOBAL "\"$name\""
1289    lappend reply_queue GetValues
1290}
1291
1292
1293proc RemoveGlobal {} {
1294    global watched_variables arguments_item_id
1295    set f [.data.t focus]
1296
1297    if {$f == $arguments_item_id || [.data.t parent $f] == $arguments_item_id} return
1298
1299    .data.t delete $f
1300
1301    if {$f == ""} return
1302
1303    set p [lsearch -exact $watched_variables $f]
1304    set watched_variables [lreplace $watched_variables $p $p]
1305}
1306
1307
1308proc GetGlobalData {data} {
1309    global current_variable watched_variables value_queue
1310
1311    if {$data == "UNKNOWN"} {
1312        .data.t set $current_variable 0 "UNKNOWN"
1313        set p [lsearch -exact $watched_variables $current_variable]
1314        set watched_variables [lreplace $watched_variables $p $p]
1315        return
1316    }
1317
1318    set node [.data.t children $current_variable]
1319
1320    if {$node == ""} {
1321        set node [.data.t insert $current_variable end]
1322        .data.t item $current_variable -open 1
1323    }
1324
1325    set val [ParseValueList $data]
1326    lassign [ValueData $val] type text addr
1327    .data.t item $node -text $type
1328    .data.t set $node 0 $text
1329    .data.t set $node 1 $addr
1330
1331    if {$addr != ""} {
1332        lappend value_queue $node
1333    }
1334}
1335
1336
1337# returns type, text and address
1338proc ValueData {val} {
1339    set c1 [string index $val 0]
1340    set rest [string range $val 1 end]
1341
1342    switch $c1 {
1343        "@" {
1344            return [list "" "..." [format "0x%x" $rest]]
1345        }
1346        "=" {
1347            switch $rest {
1348                6 {return {"BOOLEAN" "#f" ""}}
1349                22 {return {"BOOLEAN" "#t" ""}}
1350                14 {return {"NULL" "()" ""}}
1351                30 {return {"UNDEFINED" "#<undefined>" ""}}
1352                46 {return {"UNBOUND" "#<unbound>" ""}}
1353                62 {return {"EOF" "#<eof>" ""}}
1354                default {
1355                    if {($val & 15) == 10} {
1356                        return [list "CHARACTER" [format "#\%c" [expr $val >> 8]] ""]
1357                    }
1358
1359                    return [list "???" [format "#<unknown immediate value 0x%x>" \
1360                        $val] ""]
1361                }
1362            }
1363        }
1364        default {return [list "FIXNUM" $val ""]}
1365    }
1366}
1367
1368
1369proc MangleSymbol {str} {
1370    if {[regexp {^##([^#]+)#(.+)$} $str _ prefix name]} {
1371        set len [string length $prefix]
1372        return [binary format ca*a* $len $prefix $name]
1373    }
1374
1375    return $str
1376}
1377
1378
1379proc DemangleSymbol {str} {
1380    set b1 ""
1381    binary scan $str ca* b1 name
1382
1383    if {$b1 == ""} {
1384        return $str
1385    } elseif {$b1 == 0} {
1386        return "#:$name"
1387    } elseif {$b1 < 32} {
1388        return [format "##%s#%s" [string range $name 0 2] [string range $name 3 end]]
1389    }
1390
1391    return $str
1392}
1393
1394
1395proc ParseValueList {str} {
1396    set vals {}
1397
1398    while {[regexp {^\s*(\S+)(.*)$} $str _ val rest]} {
1399        lappend vals $val
1400        set str $rest
1401    }
1402
1403    return $vals
1404}
1405
1406
1407proc FirstStepReply {} {
1408    global stepping
1409    set stepping 0
1410    SendReply CONTINUE
1411}
1412
1413
1414proc ProcessData {data} {
1415    global data_queue
1416
1417    if {$data_queue == ""} {
1418        UpdateHeader "unexpected data: $data"
1419    }
1420
1421    set handler [lindex $data_queue 0]
1422    $handler $data
1423}
1424
1425
1426proc ExtractLocation args {
1427    foreach data $args {
1428        if {[regexp {^([^:]+):(\d+)(: .*)?$} $data _ fname line]} {
1429            return [list $fname $line]
1430        }
1431    }
1432
1433    return ""
1434}
1435
1436
1437proc LocateFile {fname} {
1438    global search_path
1439
1440    foreach d $search_path {
1441        set fn [file join $d $fname]
1442
1443        if {[file exists $fn]} {
1444            set fn [file normalize $fn]
1445            Log "Located: $fn"
1446            return $fn
1447        }
1448    }
1449
1450    return ""
1451}
1452
1453
1454proc InsertDebugInfo {index event args} {
1455    global file_list globals
1456    set loc [eval ExtractLocation $args]
1457
1458    # check for assignment event
1459    if {$event == 1} {
1460        set name [lindex $args 1]
1461        lappend globals($name) $index
1462    }
1463
1464    if {$loc != ""} {
1465        set fname [LocateFile [lindex $loc 0]]
1466
1467        if {[lsearch -exact $file_list $fname] == -1} {
1468            lappend file_list $fname
1469        }
1470
1471        set line [lindex $loc 1]
1472        # icky: compute array variable name from filename:
1473        set tname "file:$fname"
1474        global $tname
1475        set xname "$tname\($line\)"
1476        lappend $xname $index
1477        return 1
1478    }
1479
1480    return 0
1481}
1482
1483proc ProcessString {str} {
1484    if {$str == "#f"} {
1485        return ""
1486    } elseif {[regexp {^"(.*)"$} $str _ strip]} {
1487        return $strip
1488    } else {
1489        return $str
1490    }
1491}
1492
1493proc FetchEventListReply {} {
1494    global file_list reply_queue data_queue
1495    UpdateHeader "fetching debug information ..."
1496    lappend data_queue EventInfoData
1497    SendReply LIST_EVENTS {""}
1498}
1499
1500
1501proc EventInfoData {data} {
1502    if {[regexp {(\d+)\s+(\d+)\s+([^\s]*)\s+(.*)$} $data _ index event loc val]} {
1503        set loc [ProcessString $loc]
1504        set val [ProcessString $val]
1505        InsertDebugInfo $index $event $loc $val
1506    } else {
1507        UpdateHeader "invalid event data: $data"
1508    }
1509}
1510
1511
1512proc ContinueExecution {} {
1513    global client_file EXEC_EVENT_MASK stepping reply_queue listening
1514    global value_queue
1515
1516    if {$client_file == ""} return
1517
1518    if {![CheckListening]} return
1519
1520    UpdateHeader "executing ..."
1521
1522    if {$stepping} {
1523        set stepping 0
1524        SendReply SETMASK $EXEC_EVENT_MASK
1525        lappend reply_queue ContinueExecution
1526    } else {
1527        set value_queue {}
1528        set listening 0
1529        SendReply CONTINUE
1530    }
1531}
1532
1533
1534proc StepExecution {} {
1535    global STEP_EVENT_MASK client_file stepping listening value_queue reply_queue
1536    global watched_queue
1537
1538    if {$client_file == ""} return
1539
1540    if {![CheckListening]} return
1541
1542    if {!$stepping} {
1543        set stepping 1
1544        SendReply SETMASK $STEP_EVENT_MASK
1545        lappend reply_queue StepExecution
1546    } else {
1547        set value_queue {}
1548        set watched_queue {}
1549        set listening 0
1550        SendReply CONTINUE
1551    }
1552
1553    UpdateHeader "stepping ..."
1554}
1555
1556
1557proc StartMark {w x y} {
1558    global mark_start_index last_marked_widget
1559    set mark_start_index($w) ""
1560    set last_marked_widget $w
1561    set old [$w tag ranges mk]
1562
1563    if {$old != ""} {
1564        eval $w tag remove mk $old
1565    }
1566
1567    if {![catch {$w index "@$x,$y"} pos]} {
1568        set mark_start_index($w) $pos
1569    }
1570}
1571
1572
1573proc EndMark {w} {
1574    global mark_start_index
1575    set rng [$w tag ranges mk]
1576
1577    if {$rng != ""} {
1578        set text [eval $w get $rng]
1579        set len [string length $text]
1580        set found [$w search -all $text 1.0 end]
1581
1582        foreach f $found {
1583            $w tag add mk $f "$f + $len chars"
1584        }
1585    }
1586
1587    set mark_start_index($w) ""
1588}
1589
1590
1591proc MoveMark {w x y} {
1592    global mark_start_index
1593
1594    if {$mark_start_index($w) == ""} return
1595
1596    if {![catch {$w index "@$x,$y"} pos]} {
1597        if {$pos == $mark_start_index($w)} return
1598
1599        set old [$w tag ranges mk]
1600
1601        if {$old != ""} {
1602            eval $w tag remove $old
1603        }
1604
1605        if {[$w compare $pos < $mark_start_index($w)]} {
1606            set tmp $mark_start_index($w)
1607            set mark_start_index($w) $pos
1608            set pos $tmp
1609        }
1610
1611        $w tag add mk $mark_start_index($w) $pos
1612    }
1613}
1614
1615
1616proc FindNext {{w ""}} {
1617    global last_marked_widget
1618
1619    if {$w == ""} {set w $last_marked_widget}
1620
1621    # not sure if this test is needed
1622    if {[catch {$w index "@1,1"} pos]} return
1623
1624    while 1 {
1625        set rng [$w tag nextrange mk $pos end]
1626
1627        if {$rng == ""} return
1628
1629        lassign $rng p1 pos
1630
1631        if {[$w dlineinfo $p1] == ""} {
1632            $w see $p1
1633            return
1634        }
1635    }
1636}
1637
1638
1639proc FindPrevious {{w ""}} {
1640    global last_marked_widget
1641
1642    if {$w == ""} {set w $last_marked_widget}
1643
1644    # not sure if this test is needed
1645    if {[catch {$w index "@1,1"} pos]} return
1646
1647    set rng [$w tag prevrange mk $pos 1.0]
1648
1649    if {$rng == ""} return
1650
1651    set p1 [lindex $rng 0]
1652    $w see $p1
1653}
1654
1655
1656proc SendReply {rep args} {
1657    global client_file reply
1658    set rest ""
1659
1660    if {$args != ""} {
1661        set rest " [join $args]"
1662    }
1663
1664    set str "($reply($rep)$rest)"
1665    Log "send: $str"
1666    puts $client_file $str
1667}
1668
1669
1670proc SelectFile {} {
1671    global current_filename selected_filename
1672
1673    if {$current_filename == $selected_filename} return
1674
1675    if {![LoadFile $selected_filename]} return
1676
1677    if {[SwitchFile $selected_filename]} ApplyTags
1678}
1679
1680
1681proc OpenFile {} {
1682    global current_filename file_list
1683    set dir "."
1684
1685    if {$current_filename != ""} {
1686        set dir [file dirname $current_filename]
1687    }
1688
1689    set fname [tk_getOpenFile -title "Select source file" -initialdir $dir]
1690    set fname [file normalize $fname]
1691
1692    if {$fname == "" || $fname == $current_filename} return
1693
1694    if {[lsearch -exact $file_list $fname] == -1} {
1695        tk_messageBox -message "No debug information available for \"$fname\"" \
1696            -type ok
1697        return
1698    }
1699
1700    if {![LoadFile $fname]} return
1701
1702    if {[SwitchFile $fname]} ApplyTags
1703}
1704
1705
1706proc SwitchFile {fname} {
1707    global current_bp_lines saved_bp_lines file_list current_filename
1708
1709    Log "switch: $current_filename -> $fname"
1710
1711    if {$current_filename != ""} {
1712        Log "saving bps: $current_bp_lines"
1713        set saved_bp_lines($current_filename) $current_bp_lines
1714    }
1715
1716    set current_filename $fname
1717    Log "searching $fname in $file_list"
1718
1719    if {[lsearch -exact $file_list $fname] != -1} {
1720        if {[info exists saved_bp_lines($fname)]} {
1721            set current_bp_lines {}
1722            foreach line $saved_bp_lines($fname) {MarkBP $line}
1723            Log "restoring bps: $current_bp_lines"
1724        } else {
1725            set current_bp_lines {}
1726        }
1727
1728        return 1
1729    }
1730
1731    return 0
1732}
1733
1734
1735proc LocateEvent {loc val} {
1736    global current_filename file_list saved_bp_lines search_path last_location
1737    set loc [ExtractLocation $loc $val]
1738
1739    if {$loc != ""} {
1740        set fname0 [lindex $loc 0]
1741        set fname [LocateFile [lindex $loc 0]]
1742        set line [lindex $loc 1]
1743
1744        if {$fname != $current_filename} {
1745            if {![LoadFile $fname]} return
1746
1747            if {[SwitchFile $fname]} ApplyTags
1748        }
1749
1750        set last_location $line
1751        SetFocus $line
1752    }
1753}
1754
1755
1756proc LocateCSource {cloc} {
1757    global current_c_filename search_path
1758    set loc [ExtractLocation $cloc]
1759
1760    if {$loc != ""} {
1761        .code.h configure -text $cloc
1762        set fname [file normalize [lindex $loc 0]]
1763        set line [lindex $loc 1]
1764
1765        if {$fname != $current_c_filename} {
1766            foreach d $search_path {
1767                set fn [file join $d $fname]
1768
1769                if {[file exists $fn]} {
1770                    set fname $fn
1771                    break
1772                }
1773            }
1774
1775            if {![LoadFile $fname .code.t]} return
1776        }
1777
1778        SetCFocus $line
1779    }
1780}
1781
1782
1783proc LoadFile {fname {w .t}} {
1784    $w configure -state normal
1785    $w delete 1.0 end
1786
1787    if {[file exists $fname]} {
1788        set f [open $fname]
1789        $w insert 1.0 [read $f]
1790        close $f
1791        $w configure -state disabled
1792        return 1
1793    } else {
1794        $w insert 1.0 "File not found: \"$fname\""
1795        $w configure -state disabled
1796        return 0
1797    }
1798}
1799
1800
1801proc ApplyTags {} {
1802    global current_filename
1803    set aname "file:$current_filename"
1804    global $aname
1805    set old [.t tag ranges ev]
1806    Log "apply tags: $current_filename"
1807
1808    if {$old != ""} {
1809        eval .t tag remove $old
1810    }
1811
1812    foreach line [array names $aname] {
1813        set evts [set $aname\($line\)]
1814        .t tag add ev $line.0 "$line.0 lineend + 1 chars"
1815    }
1816
1817    UpdateHeader "events tagged"
1818}
1819
1820
1821proc Server {channel addr port} {
1822    global client_addr client_file
1823
1824    if {$client_file != ""} {
1825        close $channel
1826        return
1827    }
1828
1829    fconfigure $channel -buffering line -encoding binary -blocking 0
1830    fileevent $channel readable ProcessInput
1831    set client_addr $addr
1832    set client_file $channel
1833}
1834
1835
1836proc SetupServer {} {
1837    global debugger_port
1838    socket -server Server $debugger_port
1839    .t configure -state normal
1840    .t insert end "Waiting for connection from client ...\n"
1841    .t configure -state disabled
1842}
1843
1844
1845proc Usage {code} {
1846    global the_name
1847    set usage "Usage: $the_name "
1848    append usage {[-help] [-n] [-d] [-dir DIRNAME] [-port PORT] [PROGRAM ARGUMENTS ...]}
1849    puts stderr $usage
1850    exit $code
1851}
1852
1853
1854set load_startup_file 1
1855
1856for {set i 0} {$i < $argc} {incr i} {
1857    set arg [lindex $argv $i]
1858
1859    switch -regexp -- $arg {
1860        {^--?(h|help)$} {Usage 0}
1861        {^-dir$} {
1862            incr i
1863            lappend search_path [lindex $argv $i]
1864        }
1865        {^-n$} {set load_startup_file 0}
1866        {^-port$} {
1867            incr i
1868            set debugger_port [lindex $argv $i]
1869        }
1870        {^-d$} {set logging 1}
1871        {^-} {Usage 1}
1872        default {
1873            if {$program_name != ""} {Usage 0}
1874
1875            set program_name [lrange $argv $i end]
1876            break
1877        }
1878    }
1879}
1880
1881
1882if {$load_startup_file} {
1883    if {[file exists $env(HOME)/$startup_file]} {
1884        source $env(HOME)/$startup_file
1885    }
1886
1887    if {[file exists $startup_file]} {
1888        source $startup_file
1889    }
1890}
1891
1892
1893SetupGUI
1894SetupBindings
1895SetupServer
1896
1897if {$program_name != ""} {
1898    RunProcess $program_name
1899}
1900
1901
1902# TODO:
1903#
1904# - F2 is mostly pointless
1905# - data-view update is slow
1906#   - modify only when necessary? or are we creating too many items on the fly?
1907#   - or too much TCP-traffic?
1908# - allow spawning gdb, probably in separate terminal window(?)
1909#   - may be covered by just running "gdb <program>" as client
1910# - setting breakpoints on yet unregistered (i.e. dynamically loaded) files
1911#   is not possible - a file must be registered first
1912# - check whether "listening" check works
1913# - when retrieved data is wrong, clear queues
1914# - must watched globals be mangled, when qualified? (GET_GLOBAL)
1915# - dview: monospace font (needs tags, it seems)
1916# - https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI
1917# - gdb interface:
1918#   - toggle bp in C-source line, step/execute
1919#   - needs a way to trigger gdb from running program (in dbg-stub.c)
1920#     (send signal to self (SIGUSR2?))
1921# - allow explicit connection to debugger from Scheme code
1922# - multiple dbg-info for identical filenames will cause havoc
1923# - interrupt takes rather long (was in bignum-heavy code, try other)
1924# - bignums are shown as raw strings (uses string-type for bitvec)
1925# - how to handle threads?
Trap