2 Little Smalltalk, Version 5
4 Copyright (C) 1987-2005 by Timothy A. Budd
5 Copyright (C) 2007 by Charles R. Childers
6 Copyright (C) 2005-2007 by Danny Reinhold
7 Copyright (C) 2010 by Ketmar // Vampire Avalon
9 ============================================================================
10 This license applies to the virtual machine and to the initial image of
11 the Little Smalltalk system and to all files in the Little Smalltalk
12 packages except the files explicitly licensed with another license(s).
13 ============================================================================
14 Permission is hereby granted, free of charge, to any person obtaining a copy
15 of this software and associated documentation files (the 'Software'), to deal
16 in the Software without restriction, including without limitation the rights
17 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 copies of the Software, and to permit persons to whom the Software is
19 furnished to do so, subject to the following conditions:
21 The above copyright notice and this permission notice shall be included in
22 all copies or substantial portions of the Software.
24 THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 DEALINGS IN THE SOFTWARE.
32 "debug.st - a simple interactive debugger for Tiny Smalltalk
33 by Andy Valencia, May 2001
34 heavily modified by Ketmar // Vampire Avalon
36 To use the debugger, fileIn: this file. Then do:
38 Debug run: 'command...'
40 The given command line will be compiled (much as it would if you had
41 typed it interactively). Then the debugger will be started on this
42 command itself. Generally, you'll do a 's' (or 'step') to step down
43 from the command line into the first method call.
54 "======== String ========"
57 | c ret slot idx base top nl s |
58 "Convert a string with embedded newlines into an Array with one slot per line.
59 The newlines are not a part of these lines."
60 "Size the array of lines which will result"
62 ret := Array new: (self occurencesOf: nl) + 1.
63 "Walk the elements, assembling lines"
64 slot := base := idx := 1.
66 [idx < top] whileTrue: [
69 (s := self from: base to: idx) ifNil: [ s := '' ].
70 ret at: slot put: s removeTrailingBlanks.
76 (idx > top) ifTrue: [ idx := top ].
77 (idx > base) ifTrue: [
78 (s := self from: base to: idx) ifNil: [ s := '' ].
79 ret at: slot put: s removeTrailingBlanks.
86 "======== Class ========"
88 setDebug: aMethodName [
90 aMethodName := aMethodName asSymbol.
91 "Chase up the superclass chain, trying to find our Method"
92 cl := self. meth := nil.
95 meth := dict at: aMethodName ifAbsent: [ cl := cl superclass. nil ].
97 ('Undefined method ' + aMethodName + ' for class ' + self printString) printNl.
101 "FIXME: certain methods for classes like Char can't be recompiled with debugger now"
102 "cl == Char ifTrue: [ ^meth ]."
103 "cl isMeta ifTrue: [ ^meth ]." "debugger fails for MetaChar>>new: and maybe for others"
109 "======== DebugMethodIFace ========"
110 class: DebugMethodIFace [
111 | mth textlines bpoints active |
115 self in: obj var: #mth put: aMethod.
116 self in: obj var: #active put: false.
129 textlines ifNil: [ textlines := mth text asLines ].
134 bpoints ifNil: [ ^false ].
135 ^bpoints includes: bp.
138 putBreakAt: bp value: old [
139 bpoints ifNil: [ bpoints := Dictionary new ].
140 bpoints at: bp put: old.
145 bpoints ifNil: [ ^nil ].
146 (bpoints includes: bp) ifFalse: [ ^nil ].
147 res := bpoints at: bp.
148 bpoints removeKey: bp.
149 bpoints isEmpty ifTrue: [ bpoints := nil ].
154 bpoints ifNil: [ ^nil ].
155 (bpoints includes: bp) ifFalse: [ ^nil ].
165 "======== Method ========"
168 dbgData ifNil: [ dbgData := DebugMethodIFace newFor: self ]
177 "map the VM instruction byte pointer onto a source line #"
179 (line := self findLineForPC: bp) ifNotNil: [ ^line ].
180 'No match for ' print. bp printString printNl.
184 whatis: var in: ctx [
185 "describe a variable in this method"
190 var = 'self' ifTrue: [
191 var print. ' is a self reference' print.
192 obj := ctx arguments at: 1.
196 "name of an argument"
199 (idx := argNames indexOfVal: var) ifNotNil: [
201 var print. ' is an argument' print.
202 obj := ctx arguments at: idx.
209 tempNames ifNotNil: [
210 (idx := tempNames indexOfVal: var) ifNotNil: [
211 var print. ' is a method variable' print.
212 obj := ctx temporaries at: idx
219 (idx := (self forClass instanceVariables) indexOfVal: var) ifNotNil: [
220 var print. ' is an instance variable' print.
221 obj := ctx arguments at: 1.
222 obj := (Object class in: obj at: idx)
226 "if we found it, display the generic information"
228 ' (index ' print. idx print. ')' printNl.
229 ' Value: ' print. obj printNl.
230 ' Class: ' print. obj class printString print.
231 ' basicSize: ' print. obj basicSize printNl.
233 "couldn't find it..."
234 'Unknown variable: ' print. var printNl
238 getVar: var in: ctx ifAbsent: aBlock [
239 "get a variable in this method, return its value"
242 var = 'self' ifTrue: [ ^ctx arguments at: 1 ].
243 "name of an argument"
244 argNames ifNotNil: [ (idx := argNames indexOfVal: var) ifNotNil: [ ctx arguments at: idx + 1 ]].
246 tempNames ifNotNil: [ (idx := tempNames indexOfVal: var) ifNotNil: [ ctx temporaries at: idx ]].
248 (idx := (self forClass instanceVariables) indexOfVal: var) ifNotNil: [
249 var := ctx arguments at: 1.
250 ^(Object class in: var at: idx)
252 "couldn't find it..."
256 getVar: var in: ctx [
257 ^self getVar: var in: ctx ifAbsent: [ ^nil ]
261 "print a variable in this method"
263 obj := self getVar: var in: ctx ifAbsent: [ ('Unknown variable: ' + var) printNl. ^self ].
268 "set a breakpoint in this method"
271 "if already set, ignore"
272 (dbgData hasBreakAt: bp) ifTrue: [ ^self ].
273 "record current opcode at code location and remember breakpoint"
274 old := byteCodes at: bp + 1.
275 dbgData putBreakAt: bp value: old.
276 "update the code location if it's already active"
277 dbgData active ifTrue: [ self patchBreak: bp active: true ].
281 "remove a breakpoint in this method"
284 (dbgData hasBreakAt: bp) ifFalse: [ ^self ].
285 "restore code contents"
286 self patchBreak: bp active: false.
287 "remove record of this breakpoint"
288 dbgData delBreakAt: bp.
291 patchBreak: bp active: flag [
292 "set or clear the breakpoint instruction in the code"
294 "patch in a DoSpecial operation 12 (breakpoint)"
295 byteCodes at: (bp + 1) put: ((15*16) + 12).
297 "restore old code at this location"
298 byteCodes at: (bp + 1) put: (dbgData breakValueAt: bp).
303 "activate or deactivate breakpoints for this method"
306 (bpl := dbgData breakpoints) ifNil: [ ^self ].
307 "skip all this if we aren't changing settings"
308 (dbgData active = flag) ifTrue: [ ^self ].
309 "for each location with a breakpoint, update it"
310 bpl keysDo: [:bp | self patchBreak: bp active: flag].
311 dbgData active: flag.
315 "map source line # to a code location"
316 ^self findPCForLine: line
320 "get the DebugMethod, which has symbolic information for variables"
321 '================' printNl.
322 self forClass printString print. '>>' print. name printNl.
323 '----------------' printNl.
325 '----------------' printNl.
328 internalPrintValue: obj [
329 (obj isKindOf: Context)
331 '{' print. obj class print. '}' printNl.
337 printAllVarsIn: ctx [
341 'arguments:' printNl.
342 1 to: argNames size do: [:idx |
343 ' ' print. (argNames at: idx) print. ' = ' print.
344 self internalPrintValue: (ctx arguments at: idx + 1).
347 tempNames ifNotNil: [
348 'method vars:' printNl.
349 1 to: tempNames size do: [:idx |
350 ' ' print. (tempNames at: idx) print. ' = ' print.
351 self internalPrintValue: (ctx temporaries at: idx).
354 iv := self forClass instanceVariables.
355 iv isEmpty ifFalse: [
356 cls := ctx arguments at: 1.
357 'instance vars:' printNl.
358 1 to: iv size do: [:idx |
359 ' ' print. (iv at: idx) print. ' = ' print.
360 self internalPrintValue: (self in: cls at: idx).
371 "======== Debug ========"
372 Object subclass: Debug [
373 | proc bpoints prevList selctx lastDbgWarnMethod |
377 "blow away any selected context when we run"
379 "execute for one instruction
380 return whether or not the return was 'normal' (i.e., VM stopped due to debugger control, not something else)
381 spit out a message for a breakpoint"
382 ret := proc doExecute: count + 1.
383 (ret = 5) ifTrue: [ ^true ].
384 (ret = 6) ifTrue: [ self onBreak. ^true ].
385 (ret = 2) ifTrue: [ 'Error trap' printNl ].
386 (ret = 3) ifTrue: [ 'Message not understood' printNl ].
387 (ret = 4) ifTrue: [ 'Method returned' printNl ].
388 (ret = 7) ifTrue: [ self error: 'DEBUGGER ERROR: no "yiled" processing yet' ].
393 "get source line corresponding to current byte pointer"
394 ^(ctx method) srcLine: ctx bytePointer.
398 "show source line corresponding to current VM instruction of a given context"
402 line := self srcLine: ctx.
405 'Method ' print. meth name print.
406 ' for class ' print. meth forClass print.
407 ': no source displayed.' printNl
410 ' ' print. line print. ': ' print.
411 (meth textlines at: line ifAbsent: ['']) printNl
416 "display current line of active procedure"
417 ^self showLine: self curContext
421 "run a command line under the debugger"
422 | meth ret ctx proc |
423 meth := (LstCompiler new text: ('debugCmd ^' + line) instanceVars: #()) compileWithClass: Undefined.
428 ctx setup: meth withArguments: (Array new: 1).
437 "initialize our new debug session"
439 bpoints := Array new: 0.
443 "tell if the VM instruction pointer is at a method invocation"
444 "TODO: process unary and binary messages"
445 | ret meth ctx pc low high |
446 "get the next instruction"
449 pc := ctx bytePointer.
450 high := meth byteCodes at: (pc + 1) ifAbsent: [ ^nil ].
453 high := high div: 16.
456 low := meth byteCodes at: (pc + 1) ifAbsent: [ ^nil ].
459 "return nil if it isn't a SendMessage"
460 (high = 9) ifFalse: [ ^nil ].
461 "otherwise return the selector and return address"
463 ret at: 1 put: (meth literals at: (low + 1)).
469 "set up to step into a new method"
470 | ctx stack sp args target meth |
474 args := stack at: sp.
475 target := args at: 1.
476 meth := target class setDebug: sel.
477 meth ifNil: [ ^true ].
478 (self runIt: 1) ifFalse: [
479 'Execution done in ' print.
482 target class printNl.
489 "tell if we're at a breakpoint; as a side effect, display this fact to the user"
492 ctx ifNil: [ ^false ].
494 1 to: bpoints size do: [:idx|
495 rec := bpoints at: idx.
496 (((rec at: 1) = meth) and:
497 [(rec at: 2) = (self srcLine: ctx)])
499 'Breakpoint ' print. idx print. ' hit in ' print.
500 meth name printString print. '/' print.
509 "set a breakpoint at the instruction beyond the SendMessage"
512 "if we're within a non-debug method, just limp forward"
514 (self isDebugMethod: (meth := ctx method)) ifFalse: [
515 'doing single stepping' printNl.
521 "otherwise break beyond the call"
523 "now let it run until it hits the breakpoint, and clear the breakpoint"
524 self breakActive: true. meth breakActive: true.
525 res := self runIt: -1.
526 self breakActive: false. meth clearBreak: pc.
528 "should be stopped at the expected location"
529 ((proc context = ctx) and: [ ctx bytePointer = pc ]) ifTrue: [ ^false ].
530 "or hit some other breakpoint"
531 (self onBreak) ifTrue: [ ^false ].
532 "otherwise, what's going on?"
533 'Unexpected run completion' printNl.
536 "some other error killed us"
537 'Execution aborted' printNl.
542 "implement a single step, stepping either over or into calls (method invocations) depending on the intoCalls argument"
545 srcl := self srcLine: ctx.
546 [(proc context == ctx) and: [srcl == (self srcLine: ctx)]] whileTrue: [
547 "if dropping into a new method, deal with it"
550 "stepping into the call"
551 intoCalls ifTrue: [ ^self stepCall: (ret at: 1) ].
553 (self overCall: (ret at: 2)) ifTrue: [ ^true ].
555 "otherwise run a single VM operation"
556 (self runIt: 1) ifFalse: [
557 'Execution done at line ' print. srcl printString printNl.
565 printReg: reg in: ctx [
566 "print a VM register"
567 (reg = '$pc') ifTrue: [ ctx bytePointer print. ^self ].
568 (reg = '$sp') ifTrue: [ ctx stackTop print. ^self ].
569 'Unknown register: ' print. reg print
573 selctx ifNil: [ ^proc context ].
578 "display arguments, temporaries, instance variables, and VM registers"
580 "get the DebugMethod, which has symbolic information for variables"
581 ctx := self curContext.
582 "(self isDebugMethod: (meth := ctx method)) ifFalse: [ ^nil ]."
584 "walk each variable, printing its value"
586 var print. ': ' print.
589 var print. ' is a register variable' printNl.
591 meth whatis: var in: ctx
597 "display arguments, temporaries, instance variables, and VM registers"
599 "get the DebugMethod, which has symbolic information for variables"
600 ctx := self curContext.
601 "(self isDebugMethod: (meth := ctx method)) ifFalse: [ ^nil ]."
603 "walk each variable, printing its value"
605 var print. ': ' print.
608 self printReg: var in: ctx
610 meth print: var in: ctx
618 | s cl clname meth methname i rec lineNum inClass arg loc |
619 "map straight line # to current class/method"
621 ((arg at: 1) isDigit) ifTrue: [
622 lineNum := arg asNumber.
624 'Bad line #' print. arg printNl.
627 meth := self curContext method.
628 arg := (meth forClass printString) + '/' + (meth name printString) + '/' + lineNum printString.
630 "parse <class>:<method>"
632 (s size < 2) ifTrue: [
633 'Format is <class>/<method>' printNl.
636 "look up in instance methods unless it's Meta<class>, in which case trim the 'Meta' and look up in class methods"
638 ((clname from: 1 to: 4) = 'Meta') ifTrue: [
640 clname := clname from: 5 to: clname size
644 clname := clname asSymbol.
645 methname := (s at: 2) asSymbol.
647 (s size > 2) ifTrue: [
648 lineNum := (s at: 3) asNumber.
650 'Bad line #' print. (s at: 3) printNl.
657 cl := Smalltalk at: clname ifAbsent: [
658 ('Unknown class: ' + clname printString) printNl.
661 "convert to metaclass if needed"
662 inClass ifTrue: [ cl := cl class ].
663 "now get method, in its debuggable format"
664 meth := cl setDebug: methname.
666 ('Unknown method: ' + methname printString) printNl.
669 "(self isDebugMethod: meth) ifFalse: [ ^nil ]."
670 "if it's already set, don't do it again"
671 rec := Array with: meth with: lineNum.
672 i := bpoints indexOfVal: rec.
674 'Already set as breakpoint ' print.
678 "see if we can turn line # into a code location"
679 loc := meth codeLoc: lineNum.
681 'No code for source line ' print. lineNum printNl.
684 "set & record the breakpoint"
686 bpoints := bpoints with: rec
690 "delete an existing breakpoint"
691 | arg n rec meth lineNum |
692 arg := args at: 1 ifAbsent: ['Missing argument' printNl. ^nil].
695 ('Invalid argument: ' + arg) printNl
697 ((n < 1) or: [n > bpoints size]) ifTrue: [
698 ('No such breakpoint: ' + arg) printNl
700 rec := bpoints at: n.
702 lineNum := rec at: 2.
703 meth clearBreak: (meth codeLoc: lineNum).
704 bpoints := bpoints removeIndex: n.
705 n print. ': deleted' printNl
713 'Breakpoints:' printNl.
714 1 to: bpoints size do: [:x|
716 rec := bpoints at: x.
718 lineNum := rec at: 2.
719 meth name printString print. '/' print.
725 "make all our breakpoints active or inactive, depending on flag's value"
729 meths add: (rec at: 1)
731 meths do: [:meth| meth breakActive: flag]
736 | meth where src ctx |
737 "get the method we're going to display"
738 ctx := self curContext.
739 "(self isDebugMethod: (meth := ctx method)) ifFalse: [ ^self ]."
741 "either continue listing, or start from the given place"
742 (args size < 1) ifTrue: [
744 "list around where we're currently executing"
745 where := (self srcLine: ctx) - 5
747 where := prevList + 1
750 where := (args at: 1) asNumber.
752 'Invalid line number: ' print.
753 (args at: 1) printNl.
758 src := meth textlines.
759 where to: (where + 8) do: [:x|
760 ((x > 0) and: [x <= src size]) ifTrue: [
761 (x printString printWidth: 8) print.
769 "return next context deeper in context stack
770 because contexts are only forward linked, we have to search from the top inward, then return the next one out"
773 [(prev := c previousContext) ~= ctx] whileTrue: [
774 prev ifNil: [ ^nil ].
780 upDown: up count: args [
781 "move up or down the stack frames"
783 "if nothing selected, start from bottom of stack"
784 selctx := self curContext.
785 "get count, default 1"
786 (args size > 0) ifTrue: [
787 count := (args at: 1) asNumber
791 "walk the context chain"
792 1 to: count do: [:ignore |
793 "get next/prev context depending on step direction"
795 c := selctx previousContext
797 c := self nextContext: selctx
799 "just ignore running off the end"
800 c ifNotNil: [ selctx := c ]
805 "convert Class methods to DebugMethod's"
809 "map MetaFOO -> FOO class"
810 ((clname from: 1 to: 4) = 'Meta') ifTrue: [
818 cl := Smalltalk at: n asSymbol ifAbsent: [ nil ].
820 ifNil: [ ('Unknown class: ' + clname) printNl ]
822 "map to metaclass if needed"
823 meta ifTrue: [ cl := cl class ].
825 cl methods keysDo: [:k | cl setDebug: k ]
832 'available debugger commands' printNl.
833 '---------------------------' printNl.
834 's step\n single step to the next line' printNl.
835 'si stepi\n single step one VM instruction' printNl.
836 'n next\n step over method calls on the current line' printNl.
837 'b <class>/<method>[/<line #>] break <class>/<method>[/<line #>]' printNl.
838 ' set a breakpoint at the named method.' printNl.
839 ' meta<class> accesses class methods.' printNl.
840 ' a plain line number applies to the current class/method.' printNl.
841 'c cont\n continue running until error, completion, or breakpoint.' printNl.
842 'd int delete int\n delete breakpoint.' printNl.
843 'lb listbreak\n list breakpoints.' printNl.
844 'p varlist print varlist\n print variable(s).' printNl.
845 ' You may also use $pc (VM instruction pointer) and $sp (VM stack top pointer).' printNl.
846 'q quit\n leave the debugger (abandon the executing target code).' printNl.
847 'where bt\n show stack backtrace.' printNl.
848 'l [line] list [line]\n list source (can provide a line # as argument).' printNl.
849 'whatis\n describe variable more thoroughly.' printNl.
850 'up down\n move up and down the call stack for purposes of accessing variables.' printNl.
851 'debug class\n compiles all method''s for that class in their debuggable form.' printNl.
852 'br browse\n invokes system data structure browser.' printNl.
853 'di disasm\n disassemble current method.' printNl.
854 'allvars\n show all variables.' printNl.
855 '(blank line)\n re-enter previous command. Useful for single stepping statements in a row.' printNl.
859 "main command loop for the debugger session"
860 | prev did cmd done line ctx meth goodpt |
863 [ true ] whileTrue: [
864 (ctx := self curContext) ifNil: [ ^nil ].
865 (meth := ctx method) ifNil: [ ^nil ].
868 ifNil: [ ctx := self curContext ]
870 (meth = ctx method) ifNil: [ meth := (ctx := self curContext) method. ].
873 "show disassembled line"
874 ' ' print. meth disassemble: 0 at: ctx bytePointer for: 1.
878 meth forClass printString print. '>>' print. meth name print.
879 '(' print. ((ctx bytePointer) printWidth: 4) print. ')' print.
885 line := String input.
886 "re-insert previous command if empty line"
887 (line isEmpty) ifTrue: [ line := prev ].
890 line := line break: ' \t\n\r'.
891 "command is first, arguments follow"
893 line := line from: 2 to: line size.
894 "set flag to indicate command hasn't matched yet"
896 "step a single VM instruction"
897 (((cmd = '?') or: [ cmd = 'h' ]) or: [ cmd = 'help' ]) ifTrue: [ self showHelp. did := true ].
898 ((cmd = 'si') or: [ cmd = 'stepi' ]) ifTrue: [
900 ifTrue: [ 'Not runnable' printNl ]
903 (self runIt: 1) ifFalse: [
910 ((cmd = 'step') or: [cmd = 's']) ifTrue: [
912 ifTrue: [ 'Not runnable' printNl ]
915 done := self doStep: true
919 "step a source line, stepping over message sends"
920 ((cmd = 'next') or: [cmd = 'n']) ifTrue: [
922 ifTrue: [ 'Not runnable' printNl ]
925 done := self doStep: false
930 ((cmd = 'p') or: [cmd = 'print']) ifTrue: [
935 (cmd = 'whatis') ifTrue: [
940 ((cmd = 'b') or: [cmd = 'break']) ifTrue: [
945 "clear breakpoint(s)"
946 ((cmd = 'd') or: [cmd = 'delete']) ifTrue: [
947 self clearBreak: line.
951 ((cmd = 'lb') or: [cmd = 'listbreak']) ifTrue: [
956 ((cmd = 'cont') or: [cmd = 'c']) ifTrue: [
957 "clear previous listing position"
959 "step forward once, even over a breakpoint"
960 done := (self runIt: 1) not.
961 "now run until completion or next break"
963 "activate, run, and deactivate"
964 self breakActive: true.
965 done := (self runIt: -1) not.
966 self breakActive: false.
967 "display a message if hit a breakpoint"
968 done ifFalse: [ self onBreak ].
973 ((cmd = 'l') or: [cmd = 'list']) ifTrue: [
978 ((cmd = 'q') or: [cmd = 'quit']) ifTrue: [
982 ((cmd = 'where') or: [cmd = 'bt']) ifTrue: [
985 'Process has terminated' printNl
987 proc context backtrace
991 "go up or down the stack chain"
992 ((cmd = 'up') or: [cmd = 'down']) ifTrue: [
993 self upDown: (cmd = 'up') count: line.
996 "make all procedures of the named class debuggable"
997 (cmd = 'debug') ifTrue: [
998 self makeDebug: line.
1001 "hook to data structure browser"
1002 ((cmd = 'br') or: [cmd = 'browse']) ifTrue: [
1007 ((cmd = 'di') or: [cmd = 'disasm']) ifTrue: [
1011 (cmd = 'allvars') ifTrue: [
1012 meth printAllVarsIn: ctx.
1016 did ifFalse: [ 'Unknown command.' printNl ].
1022 "get the DebugMethod, which has symbolic information for variables"
1023 ctx := self curContext.
1024 "(self isDebugMethod: (meth := ctx method)) ifFalse: [ ^nil ]."
1026 meth forClass printString print. '>>' print. meth name printNl.