Diagnostic tools and debugging

published: 2 June 2019 / updated 26 April 2021

Lire cette page en français

 


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:

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...

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:

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:

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.