~ chicken-core (chicken-5) /feathers.tcl
Trap1#!/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?