\ vandys \ debug.f \ Implement a simple debugger \ This debugger is influenced in spirit, if not implementation details, \ by Joerg Plewe's ANSI Forth debugger. Like his, we implement the \ debugger by inserting our actions into the code generation process. \ However, we do this not because it's the only "allowed" place, but \ rather because we have no centralized "next" (and switching to one \ would cost us 2x slowdown in execution speed, I tried it). \ \ The compiler has a call to the 'genhook vector, which is usually \ a no-op. [+debug] instead switches this to a routine which emits \ an @execute via 'trap instead. We then insert appropriate checking \ in a trap handling routine, and drop into the debugger when needed. only extensions definitions \ vandys \ Vector to our 'trap handler, and our 'abortTrap handler variable (''trap) variable (''atrap) \ Ranges of addresses to trap as breakpoints 8 constant MAXBPT create (bpts) MAXBPT cells 2* allot create (nbpt) 0 , \ Flag to break ASAP--for "single" stepping create (stepping?) 0 , \ Flag that we're in debug command mode create (indebug?) 0 , \ List of words explicitly breakpointed by user create (ubpts) MAXBPT cells allot create (nubpt) 0 , \ Convert tick addr into address of first high-level word pointer : >hilev ( a -- a' ) cell+ cell+ ; \ vandys \ Convert addr to beginning of hilev definition \ This is *very* machine specific; it looks for the signature of \ the assembly code header leading into a high-level definition, \ and thus must be adjusted for targets other than the x86. : hilev? ( a -- bool ) @ $E82E2E2E = ; : hilev ; \ Convert index into breakpoint slot : >bpt ( u -- a ) cells 2* (bpts) + ; \ Find the slot matching our address, or return false : (findBpt) ( a -- F | u T ) (nbpt) @ 0 ?do i >bpt 2@ >r over u>= if r> over u<= if drop i true unloop exit then else r> drop then loop drop false ; \ vandys \ Find the ending address of a high-level routine \ Assumes there's a (;) at the end; not true for kernel words (yet?) : >endCode ( a -- a' ) begin dup @ ['] (;) - while cell+ repeat ; \ Set a breakpoint : (setBpt) ( cfaLow cfaHigh -- ) swap dup (findBpt) if >bpt >r 2dup r> 2@ d= if 2drop exit then then (nbpt) @ dup MAXBPT >= abort" Too many breakpoints" >bpt 2! 1 (nbpt) +! ; \ Clear a breakpoint : (clrBpt) ( cfa -- ) begin dup (findBpt) while dup >bpt dup cell+ cell+ swap rot (nbpt) @ swap 1+ - cells 2* move -1 (nbpt) +! repeat drop ; \ Convert index to user breakpoint word : >ubpt cells (ubpts) + ; \ vandys : (clrUbpt) ( cfa -- ) (nubpt) @ 0 ?do i >ubpt @ over = if drop i >ubpt dup cell+ swap (nubpt) @ i - 1- cells move -1 (nubpt) +! unloop exit then loop drop ; : (setUbpt) ( cfa -- ) dup (clrUbpt) \ Just in case (nubpt) @ dup MAXBPT >= abort" Too many breakpoints" >ubpt ! 1 (nubpt) +! ; : (setTraps) ( ? -- ) 0= if 'trap off 'abortTrap off exit then (''trap) @ 'trap ! (''atrap) @ 'abortTrap ! ; : (syncBpt) ( -- ) (nubpt) @ dup (nbpt) ! 0 ?do i >ubpt @ dup i >bpt 2! loop (nbpt) @ (setTraps) ; \ vandys \ Show successive memory locations, return on CR : (showMem) ( a -- ) begin dup (prval) ." : " dup @ (showval) cr key case 13 of drop exit endof \ Leave on CR or 'q' [char] q of drop exit endof [char] - of cell- endof \ Back up one cell on '-' [char] + of cell+ endof \ Forward one cell on '+' or space key 32 of cell+ endof ." ?" endcase again ; \ Set a break across an entire function : (breakFn) ( cfa -- ) dup @ ['] exit = if \ Break at the calling function if we're going to execute "exit" drop r> r> r@ swap >r swap >r then endCode (setBpt) ; \ Stack backtrace, with calling frame knowledge vandys [ifndef] #locals 8 constant #locals variable 'end}-- [then] : .frame ( frame -- 'frame ptr-rp ) @+ 'end}-- = ( frame extended? ) swap dup #locals (dumpw) #locals cells + ( extended? frame+ ) @+ -rot ( 'frame extended? frame++ ) swap if cell+ cell+ @+ ." in: " (showval) cr then ; scrLocal : xwhere ( -- ) cr (local) @ rp@ begin ( frame ptr-rp ) dup rp0 @ < while 2dup = if drop .frame else @+ (showval) cr then repeat 2drop ; Local defer .src \ Defined later : .srcs ( -- ) rp@ begin dup @ dup .src dup (prval) ." : " @ (prval) cr ( 'rp ) key dup [char] < = if drop cell+ rp0 @ min else [char] > = if cell- rp@ max else drop exit then then again ; \ vandys \ Main UI of debugger mode defer Undebug : (debugger) ." Debugger at " r@ dup (prval) ." : " @ (prval) cr (syncBpt) (stepping?) off (indebug?) on begin .s cr ." debug>" key case 32 of ." next" cr r@ (breakFn) (indebug?) off exit endof [char] a of 1 abort" Aborted" endof [char] c of ." continue" cr (syncBpt) (indebug?) off exit endof [char] d of ." delete breakpoint" cr r@ hilev (setUbpt) (syncBpt) ; : undebug ( -- ) ' >hilev (clrUbpt) (syncBpt) ; :noname (bpts) MAXBPT cells 2* erase (ubpts) MAXBPT cells erase 'trap off 'abortTrap off (nbpt) off (nubpt) off (stepping?) off ; is Undebug : (findBpt?) (findBpt) dup if swap drop then ; \ Source code access support--data structures, init, DB store vandys variable 'tagSrc Local variable srcMap variable nMap variable srcHigh struct src intcell ca intcell blk intcell off endstruct : entry! ( a-entry -- ) blk @ over src>blk ! >in @ swap src>off ! ; Local \ Note: bootup init is at end of this source file (boot-srcMap) \ Source code access support--creating blk/line# database vandys : update? ( -- ? ) here srcHigh @ > if here srcHigh ! false exit then srcMap @ nMap @ 0 ?do here over src>ca @ = if entry! drop unloop true exit then src.size + loop drop false ; scrLocal : growMap ( -- a-entry ) nMap inc srcMap @ ( a-map ) nMap @ src.size * tuck bkrealloc dup srcMap ! ( u-sz a-map' ) swap src.size - + ; scrLocal : tagSrc ( -- ) blk @ 0= if exit then update? if exit then growMap here over src>ca ! entry! ; scrLocal : +g ( -- ) ['] tagSrc 'tagSrc ! ; : -g ( -- ) 'tagSrc off ; \ Source code access support--search DB vandys : (entry@) ( ca -- a-entry T | F ) srcMap @ nMap @ 0 ?do ( ca a-entry ) 2dup src>ca @ = if unloop nip true exit then src.size + loop 2drop false ; scrLocal 4 constant backDistance scrLocal : entry@ ( ca -- a-entry T | F ) backDistance 0 do dup (entry@) if unloop nip true exit then cell- loop drop false ; Local \ Source code access support--display support vandys 2 constant nearLines Local : .lines ( line# buf -- ) swap dup nearLines + swap do ( buf ) i 0 BLKROWS within if dup i BLKCOLS * + BLKCOLS -trailing type cr then loop drop ; Local variable pos scrLocal variable line scrLocal variable idx scrLocal : .srcLine ( line pos -- ) dup BLKCOLS < if 1- then tuck pos ! line ! ( pos' ) begin 1- dup line @ + c@ ( pos' c ) bl = dup if swap 1+ swap then over 0= or until idx ! line @ idx @ type inv-on line @ idx @ + pos @ idx @ - type inv-off line @ pos @ + BLKCOLS pos @ - -trailing type cr ; Local \ Source code access support--display source vandys variable buf scrLocal variable line# scrLocal variable pos2 scrLocal :noname ( : .src ) ( ca -- ) entry@ 0= if exit then ( entry ) ." Block" dup src>blk @ . ." , line" dup src>off @ BLKCOLS / . cr dup src>blk @ block buf ! src>off @ dup pos2 ! ( pos ) BLKCOLS / dup line# ! nearLines - buf @ .lines buf @ line# @ BLKCOLS * + pos2 @ BLKCOLS mod .srcLine line# @ 1+ buf @ .lines ; is .src \ Trap handling vandys variable 'sniffer ( word is called { ? -- ? | T } ) : ('trap) ( -- ) r@ cell- (findBpt?) (stepping?) @ or 'sniffer @execute if branch [ ' (debugger) >hilev , ] then ; : ('atrap) ( a -- a ) (indebug?) @ if exit then ." Abort: " dup count type cr blk @ ?dup if ." Block" . >in @ ." line" dup BLKCOLS / 1+ . ." column" BLKCOLS mod 1+ . cr then branch [ ' (debugger) >hilev , ] ; ' ('trap) >hilev (''trap) ! ' ('atrap) (''atrap) ! : (trap) ( -- ) 'trap @ ?dup if >r then ; : (genTrap) ( -- ) ['] (trap) , 'tagSrc @execute ; \ vandys \ Disable and enable debug code generation. Note the words are \ immediate, so it can be done at a granularity finer than a \ single word definition. : [+debug] ['] (genTrap) 'genhook ! ; immediate : [-debug] 0 'genhook ! ; immediate \ Stop at the next stoppable point; most useful for breaking in \ an OO method : breakNext ( -- ) (stepping?) on true (setTraps) ; \ Hook to init src code DB; "definitions" clears Local names, so we put \ this at the tail of the source code also initialize definitions : boot-srcMap ( ? -- u | ) if srcMap off nMap off srcHigh off exit then 20000 ; only