Diagnostic tools and debugging
published: 2 June 2019 / updated 26 April 2021
FORTH is a very powerful programming language because it has various tools assistance that can be used in interpretation or compilation.
The stack and digital base monitor
FORTH allows a lot of boldness, but can also make you lose delicate situations. In the case of a compilation that fails, the digital base change, allowed in both compilation and interpretation, can turn against the programmer:
: TEST ( ---) [ HEX ] ASCII A DDUP \ DDUP don't exist [ DECIMAL ] ;
At the time of compilation, the failure suffered at the time of the attempted treatment of the
word DDUP
will leave the system in a hexadecimal numeric base. If then we try
memory accesses by invoking decimal numeric base addresses, we will suffer small inconveniences.
Specific Flash Forth Part
To avoid getting lost, the availability indicator of the FORTH interpreter, marked by 'OK', may be supplemented by additional indications.
decimal ok<#,ram> hex ok<$,ram> bin ok<%,ram>
After "ok", we see this: #,ram>. Here, the sign '#' indicates that we are in decimal place. Here are the different signs that appear:
- # we are in decimal base
- $ we are in hexadecimal base
- % we are in a binary base
- ? we are in a numeric base other than decimal, hexadecimal or binary ...
Now, let's stack some whole values:
10 ok<#,ram> 10 20 ok<#,ram> 10 20 35 ok<#,ram> 10 20 35 42 ok<#,ram> 10 20 35 42
At each value stack, in interpreted mode, these values appear to the right of ok<#,ram>.
The integers on the right are the values at the top of the stack.
This visibility of the parameters placed in the data stack will allow you to test word for word complex procedures before placing them in a definition to compile.
The decompiler
This part is gForth specific
In a conventional compiler, the source code is transformed into object code containing the reference addresses to a library equipping the compiler. To have an executable code, we must link the object code. At no time the programmer can not access the executable code contained in its libraries with the only compiler resources.
With gForth, the developer can decompile his routine, but also visualize predefined primitives so understand the operation of its program or FORTH in its entirety.
To compile a word or definition, just type SEE
followed by the word to decompile. Example of compilation with gForth:
: C>F ( øC --- øF) \ Conversion Celsius in Fahrenheit 9 5 */ 32 + ; ok see c>f \ display: : C>F 9 5 */ 32 + ; ok
Let's test SEE
on a predefined vocabulary word:
see WORDS : words context @ wordlist-words ; ok see dup Code dup ( $402EFA ) mov dword ptr 42A6F8 , ebx \ $89 $1D $F8 $A6 $42 $0 ( $402F00 ) mov eax , dword ptr [esi] \ $8B $6 ( $402F02 ) sub esi , # 4 \ $83 $EE $4 ( $402F05 ) add ebx , # 4 \ $83 $C3 $4 ( $402F08 ) mov dword ptr [esi] , eax \ $89 $6 ( $402F0A ) mov edi , dword ptr FC [ebx] \ $8B $7B $FC ( $402F0D ) jmp 4014A4 \ $E9 $92 $E5 $FF $FF end-code ok
Here we have just decompile words
then dup
. We
finds that words
is written in FORTH, while words
is written in assembly language... The gForth decompiler is also a disassembler!
Memory DUMP
Sometimes it is desirable to be able to see the values that are in memory.
The word dump
accepts two parameters: the starting address of the "dump" memory
and the number of bytes to view. On gForth:
' c>f 32 dump 7FA843D8: A6 14 40 00 00 00 00 00 - 63 FA 9A 7F 09 00 00 00 ..@.....c....... 7FA843E8: 76 FA 9A 7F 05 00 00 00 - EC 20 40 00 90 FA 9A 7F v........ @..... ok
We had compiled the word c>f
. By doing ' c>f
we point
on the field code field and it is asked to see 32 bytes. It does not serve rigorously
nothing, it's just for the example... p>
On Flash Forth:
' c>f 32 dump 12806 :08 149 242 239 226 04 255 255 255 255 255 255 255 255 255 255 ................ 12822 :255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 ................ ok<#,ram>
Ah damn. The result is in decimal. Let's resume in hexadecimal:
hex ok<$,ram> ' c>f 32 dump 3206 :08 95 f2 ef e2 04 ff ff ff ff ff ff ff ff ff ff ................ 3216 :ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ 3226 :ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ ok<$,ram>
Well, after, must decipher the hexadecimal numbers...
Change the prompt
This part is specific to FlashForth
In interpreted mode, FlashForth displays an availability text:
<#,ram> ok
You can change this prompt with its own availability indicator:
: myPrompt ." Ready! " ; ' myPrompt is prompt
The word prompt
is a vectorized execution word. It will display as prompt what
you want, here myPrompt
in our example.
The default word when FlashForth is launched is .st
. We will restore
this word after testing our example above:
' .st is prompt
It may be convenient to display the contents of other information than the data stack. We will add the number of elements still available for the data stack:
\ display stack deepth beetween () : .sd ( ---) sp@ s0 - 2 / base @ >r decimal 0 <# 41 hold #s 40 hold #> type r> base ! ; : myPrompt ( ---) .sd .st ; ' myPrompt is prompt
Example of display of the modified prompt:
ok(77)<#,ram> 1 2 3 4 5 6 7 8 ok(69)<#,ram> 1 2 3 4 5 6 7 8 + ok(70)<#,ram> 1 2 3 4 5 6 15 + ok(71)<#,ram> 1 2 3 4 5 21 + ok(72)<#,ram> 1 2 3 4 26 + ok(73)<#,ram> 1 2 3 30 + ok(74)<#,ram> 1 2 33 + ok(75)<#,ram> 1 35 + ok(76)<#,ram> 36 . 36 ok(77)<#,ram>
Step by step execution
This part is gForth specific
gForth is available with several engines:
- gforth which is the standard version with a powerful compiler;
- gforth-itc which is a version allowing the tracing of words in progress focus...
For the rest, we'll use gforth-itc. In Windows, go to the directory where is registered gForth and run gforth-itc. In Linux, open a terminal window and type gforth-itc:
When you create a new word there's often the need to check whether it behaves correctly
or not. You can do this by typing dbg badword
. A debug session might look like this:
: badword 0 DO i . LOOP ;
Use of dbg
:
2 dbg badword : badword Scanning code... #0 Nesting debugger ready! [ 1 ] 00002 7FED8F771320 7FED8F6E79F8 -> [ 2 ] 00002 00000 7FED8F771330 7FED8F6E75F0 #0 #0 2>r -> [ 0 ] 7FED8F771338 7FED8F6E77E0 i -> [ 1 ] 00000 7FED8F771340 7FED8F6F1050 . -> 0 [ 0 ] 7FED8F771348 7FED8F6E7410 loop -> [ 0 ] 7FED8F771338 7FED8F6E77E0 i -> [ 1 ] 00001 7FED8F771340 7FED8F6F1050 . -> 1 [ 0 ] 7FED8F771348 7FED8F6E7410 loop -> [ 0 ] 7FED8F771360 7FED8F6E71D0 ; -> ok
Each line displayed is one step. You always have to hit return to execute the next word that is displayed. If you don't want to execute the next word in a whole, you have to type n for nest. Here is an overview what keys are available:
- <return> Next; Execute the next word.
- n Nest; Single step through next word.
- u Unnest; Stop debugging and execute rest of word. If we got to this word with nest, continue debugging with the calling word.
- d Done; Stop debugging and execute rest.
- s Stop; Abort immediately.
Debugging large application with this mechanism is very difficult, because you have to nest very deeply into the program before the interesting part begins. This takes a lot of time.
To do it more directly put a BREAK:
command into your source code. When program
execution reaches BREAK:
the single step debugger is invoked and you have all the features
described above.
If you have more than one part to debug it is useful to know where the program has stopped at
the moment. You can do this by the BREAK" string"
command. This behaves like
BREAK:
except that string is typed out when the breakpoint is reached.
Example:
\ Displaying meta-compilation options : .options ( -- ) break" debug .options" cr ." OPTIONS :" cr FRENCH ?\ ." En-têtes (longueur) WIDTH " ENGLISH ?\ ." Header (length) WIDTH " GERMAN ?\ ." Namen (lange) WIDTH " WIDTH .flag cr FRENCH ?\ ." Compilation asservie CHECKING " ENGLISH ?\ ." Controlled compilation CHECKING " GERMAN ?\ ." Kontrolierte Compilation CHECKING " CHECKING .flag cr FRENCH ?\ ." Sauvegarde de la cible SAVING " ENGLISH ?\ ." Saving the target SAVING " GERMAN ?\ ." TARGET abspeichern SAVING " SAVING .flag cr FRENCH ?\ ." Fichier de sauvegarde TARGET$ " ENGLISH ?\ ." Output file TARGET$ " GERMAN ?\ ." Files Name abspeichert in TARGET$ " TARGET$ $@ type cr ;
You can insert BREAK" string"
several times. The chain at
insert after BREAK"
will be displayed at the start of the debugging phase.